diff --git a/.travis.yml b/.travis.yml index 3a6836590..42863e8b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,12 @@ +sudo: false language: node_js +notifications: + email: false node_js: - 0.8 - 0.10 - 0.12 + - 4.0 - iojs before_install: - npm install -g npm@~1.4.6 diff --git a/History.md b/History.md index a254fe5ff..200b9e1b2 100644 --- a/History.md +++ b/History.md @@ -1,19 +1,108 @@ +0.11.0 / 2015-10-31 +================= +* [ENHANCEMENT] Now passing request to services in server.js. (#769) +* [ENHANCEMENT] Adding the ability to add headers in client requests. (#770) +* [MAINTENANCE] Adding gitter badge to README and disabling issues. (#731) +* [FIX] Stop sending Object prototype methods as XML. (#699) + +0.10.3 / 2015-10-23 +================= +* [ENHANCEMENT] Adding createErroringStub to soap-stub. (#765) +* [ENHANCEMENT] `forceSoap12Headers` option to add SOAP v1.2 request headers. (#755) + +0.10.2 / 2015-10-22 +================= +* [ENHANCEMENT] Adding security to soap-stub. (#764) + +0.10.1 / 2015-10-22 +================= +* [ENHANCEMENT] Adding soap-stub. (#763) + +0.10.0 / 2015-10-21 +================= +* [FIX] xml namespace/element/type handling. (#756) + +0.9.5 / 2015-10-15 +================= +* [FIX] Allow circular XSD files to be loaded. (#745) +* [ENHANCEMENT] Timestamp is now optional. (#735) +* [DOC] Formatting History.md 0.9.4 notes. + +0.9.4 / 2015-09-28 +================= +* [MAINTENANCE] Adding node v4.0 to .travis.yml. (#729) +* [MAINTENANCE] Increasing mocha test timeout to 10 seconds. (#732) +* [FIX] Resolve element references when other types are referenced. (#725) +* [DOC] Update Readme.md +* [ENHANCEMENT] New Ignorebasenamespaces option. (#716) +* [ENHANCEMENT] Add optional statusCode on soap fault. (#715) +* [FIX] Fix for wsdl retrieval using soap.createClient with special options.httpClient. Before this, the specified client was not used when fetching the wsdl file. This fix will force the wsdl to use the specified httpClient. (#714) +* [FIX] Allow WSDL to be loaded from HTTPS sites. (#694) + +0.9.3 / 2015-09-08 +================= +* [ENHANCEMENT] Allow namespace overriding for elements. (#709) +* [MAINTENANCE] Disable travis emails. + +0.9.2 / 2015-09-08 +================= +* [ENHANCEMENT] Add support for xsd element ref. (#700) +* [MAINTENANCE] Moving travis build to containers. +* [MAINTENANCE] Add request sample for an operation without any parameters. (#703) +* [DOC] update spelling and formatting to clarify several sections of Readme. (#708) +* [ENHANCEMENT] Add the correct namespace alias for operations without parameters by simply removing the special case where input.parts is empty. If special logic is wanted for this case, it should be contained in objectToRpcXML in any case. (#703) +* [FIX] Fix a typo in WSDL#findChildParameterObject. (#686) +* [FIX] Fixed SOAP Fault errors not being raised as errors. (#676) +* [FIX] Use diffrent namespace styles for soap fault 1.1 and 1.2. (#674) + +0.9.1 / 2015-05-30 +================= +* [FIX] Received empty Strings are now returned as empty String rather than an empty Object. (#637) + +* [FIX] Get current namespace when parent namespace is an empty String. Fixes #533. (#661) + +* [DOC] Update README.md with documentation for #660 introduced customization of `httpClient` and `request` libs in `client.options`. (#664) + +* [FIX] Take configured "ignored namespaces" into account when processing `objectToXml()`. Fixes #537. (#662) + +* [LIC] Update license attribute to follow the new [npm conventions](https://docs.npmjs.com/files/package.json#license). (#663) + +* [ENHANCEMENT] Add ability to customize `http` client / `request` lib on client creation. (#660) + +* [FIX] Support `xsi:type` Schema on Element. Fixes #606. (#639) + +* [FIX] Make parsing of recursive Elements in `wsdl` work. (#658) + 0.9.0 / 2015-05-18 ================= * [FIX] Fix to allow request options and headers to persist for all includes. Fix to properly handle when an import/include starts with a schema element. (#608) + * [FIX] Do not end request for keep-alive connections (#600) + * [ENHANCEMENT] Added Client 'response' event (#610) + * [FIX] If response is json, then error should not be thrown. Fix issue #580 (#581) + * [FIX] Sub-namespace should be correct regardless of order of enumeration i.e. should not be overriden by other prop's namespace (#607) + * [DOC] Added a section about Server Events to README.md (#596) + * [ENHANCEMENT] Added Server 'request' event (#595) + * [ENHANCEMENT] Add support for One-Way Operations (#590) + * [FIX] `lib/wsdl.js` util function `extend()` doesn't throw an Error when handling elements that are not objects. (#589) + * [ENHANCEMENT] ClientSSLSecurity now accepts a `ca`-certificate. (#588) + * [ENHANCEMENT] ClientSSLSecurity should be able to take a Buffer as `key` and `cert` parameter. Additionally the certificates are checked whether they are correct or not (starting with `-----BEGIN`). (#586) + * [ENHANCEMENT] Add support for sending NULL values (#578) + * [ENHANCEMENT] Follow 302 redirects, don't mix quotes (#577) + * [DOC] Update CONTRIBUTING.md + * [FIX] Respond with security timestamp if request had one (#579) @@ -68,39 +157,39 @@ The `$xml` key is used to pass an `XML` Object to the request without adding a n 0.6.0 / 2014-10-29 ================= -* Enhancement: Adding bearer security type Exporting security type for usage. -* Enhancement: The qualified elementFormQualified must be respected only when the current element is not a global element. The namespace attribute is only needed if it's not included in the xmlns. -* Fix: Remove automatic port appending to "Host" header. -* Fix: Avoid creating soap:Header container when there are no children. -* Fix: Allowing a 'null' argument for WSDL methods that take no arguments. -* Fix: Wrong initialization of xmlns array when handling rpc stype wsdl. -* Fix: Fault handling. err should be used less frequently now. -* Fix: Added checking if there is input and output for operations under bindings section. -* Fix: XSD conflict with same namespace. +* [ENHANCEMENT] Adding bearer security type Exporting security type for usage. +* [ENHANCEMENT] The qualified elementFormQualified must be respected only when the current element is not a global element. The namespace attribute is only needed if it's not included in the xmlns. +* [FIX] Remove automatic port appending to "Host" header. +* [FIX] Avoid creating soap:Header container when there are no children. +* [FIX] Allowing a 'null' argument for WSDL methods that take no arguments. +* [FIX] Wrong initialization of xmlns array when handling rpc stype wsdl. +* [FIX] Fault handling. err should be used less frequently now. +* [FIX] Added checking if there is input and output for operations under bindings section. +* [FIX] XSD conflict with same namespace. 0.5.1 / 2014-07-11 ================= -* Enhancement: Add "defaults" parameter to BasicAuthSecurity's constructor -* Enhancement: Added possibility to set a custom `valueKey` for the parsed values from XML SOAP Response -* Fix: don't append port 80 to Host if not needed -* Fix: Remove possible existing BOM characters from XML String before passing it to `WSDL#_fromXML()` and parsing it. -* Fix: Handling nil attributes in response xml +* [ENHANCEMENT] Add "defaults" parameter to BasicAuthSecurity's constructor +* [ENHANCEMENT] Added possibility to set a custom `valueKey` for the parsed values from XML SOAP Response +* [FIX] don't append port 80 to Host if not needed +* [FIX] Remove possible existing BOM characters from XML String before passing it to `WSDL#_fromXML()` and parsing it. +* [FIX] Handling nil attributes in response xml 0.5.0 / 2014-07-11 ================= -* Enhancement: Allowing namespace prefixes to be ignored via config. -* Enhancement: wsdl should handle more types -* Fix: Handle defined messages ending with "Response", "Out", or "Output" -* Fix: Adding default attributesKey to server and allowing the property to be configurable fixing issue #406 -* Fix: Remove extra characters before and after soap envelope -* Fix: Allow operations to not have definitions -* Fix: Ignore unknown elements -* Fix: Keep ns from top-level -* Fix: Check status code of invocation response +* [ENHANCEMENT] Allowing namespace prefixes to be ignored via config. +* [ENHANCEMENT] wsdl should handle more types +* [FIX] Handle defined messages ending with "Response", "Out", or "Output" +* [FIX] Adding default attributesKey to server and allowing the property to be configurable fixing issue #406 +* [FIX] Remove extra characters before and after soap envelope +* [FIX] Allow operations to not have definitions +* [FIX] Ignore unknown elements +* [FIX] Keep ns from top-level +* [FIX] Check status code of invocation response 0.4.7 / 2014-06-16 ================= -* Allow request elements to have both content and attributes. +* [ENHANCEMENT] Allow request elements to have both content and attributes. 0.4.6 / 2014-06-16 ================= diff --git a/Readme.md b/Readme.md index 80d2c0e4b..5e6602e56 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,5 @@ -# Soap [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] +# Soap [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Gitter chat][gitter-image]][gitter-url] + > A SOAP client and server for node.js. This module lets you connect to web services using SOAP. It also provides a server that allows you to run your own SOAP services. @@ -18,6 +19,15 @@ Install with [npm](http://github.com/isaacs/npm): ``` npm install soap ``` + +## Where can I file an issue? + +We've disabled issues in the repository and are now solely reviewing pull requests. The reasons why we disabled issues can be found here [#731](https://github.com/vpulim/node-soap/pull/731). + +If you're in need of support we encourage you to join us and other `node-soap` users on gitter: + +[![Gitter chat][gitter-image]][gitter-url] + ## Module ### soap.createClient(url[, options], callback) - create a new SOAP client from a WSDL url. Also supports a local filesystem path. @@ -40,6 +50,7 @@ The `options` argument allows you to customize the client with the following pro - endpoint: to override the SOAP service's host specified in the `.wsdl` file. - request: to override the [request](https://github.com/request/request) module. - httpClient: to provide your own http client that implements `request(rurl, data, callback, exheaders, exoptions)`. +- forceSoap12Headers: to set proper headers for SOAP v1.2 ### soap.listen(*server*, *path*, *services*, *wsdl*) - create a new SOAP server that listens on *path* and provides *services*. *wsdl* is an xml string that defines the service. @@ -68,6 +79,14 @@ The `options` argument allows you to customize the client with the following pro name: headers.Token }; } + + // You can also inspect the original `req` + reallyDeatailedFunction: function(args, cb, headers, req) { + console.log('SOAP `reallyDeatailedFunction` request from ' + req.connection.remoteAddress) + return { + name: headers.Token + }; + } } } } @@ -81,9 +100,9 @@ The `options` argument allows you to customize the client with the following pro soap.listen(server, '/wsdl', myService, xml); ``` -### server logging +### Server Logging -If the log method is defined it will be called with 'received' and 'replied' +If the `log` method is defined it will be called with 'received' and 'replied' along with data. ``` javascript @@ -122,6 +141,21 @@ object with a `Fault` property. }; ``` +To change the HTTP statusCode of the response include it on the fault. The statusCode property will not be put on the xml message. + +``` javascript + throw { + Fault: { + Code: { + Value: "soap:Sender", + Subcode: { value: "rpc:BadArguments" } + }, + Reason: { Text: "Processing Error" }, + statusCode: 500 + } + }; +``` + ### SOAP Headers A service method can look at the SOAP headers by providing a 3rd arguments. @@ -153,9 +187,9 @@ First parameter is the Headers object; second parameter is the name of the SOAP method that will called (in case you need to handle the headers differently based on the method). -### server security example using PasswordDigest +### Server security example using PasswordDigest -If server.authenticate is not defined no authentation will take place. +If `server.authenticate` is not defined then no authentication will take place. ``` javascript server = soap.listen(...) @@ -167,10 +201,10 @@ If server.authenticate is not defined no authentation will take place. }; ``` -### server connection authorization +### Server connection authorization -This is called prior to soap service method -If the method is defined and returns false the incoming connection is +The `server.authorizeConnection` method is called prior to the soap service method. +If the method is defined and returns `false` then the incoming connection is terminated. ``` javascript @@ -183,7 +217,7 @@ terminated. ## Client -An instance of Client is passed to the soap.createClient callback. It is used to execute methods on the soap service. +An instance of `Client` is passed to the `soap.createClient` callback. It is used to execute methods on the soap service. ### Client.describe() - description of services, ports and methods as a JavaScript object @@ -205,8 +239,8 @@ An instance of Client is passed to the soap.createClient callback. It is used t ### Client.setSecurity(security) - use the specified security protocol `node-soap` has several default security protocols. You can easily add your own as well. The interface is quite simple. Each protocol defines 2 methods: -* addOptions - a method that accepts an options arg that is eventually passed directly to `request` -* toXML - a method that reurns a string of XML. +* `addOptions` - a method that accepts an options arg that is eventually passed directly to `request` +* `toXML` - a method that returns a string of XML. By default there are 3 protocols: @@ -219,9 +253,9 @@ By default there are 3 protocols: ####ClientSSLSecurity _Note_: If you run into issues using this protocol, consider passing these options as default request options to the constructor: -* rejectUnauthorized: false -* strictSSL: false -* secureOptions: constants.SSL_OP_NO_TLSv1_2//this is likely needed for node >= 10.0 +* `rejectUnauthorized: false` +* `strictSSL: false` +* `secureOptions: constants.SSL_OP_NO_TLSv1_2` (this is likely needed for node >= 10.0) ``` javascript client.setSecurity(new soap.ClientSSLSecurity( @@ -259,6 +293,16 @@ as default request options to the constructor: // result is a javascript object }) ``` +###Overriding the namespace prefix +`node-soap` is still working out some kinks regarding namespaces. If you find that an element is given the wrong namespace prefix in the request body, you can add the prefix to it's name in the containing object. I.E.: + +```javascript + client.MyService.MyPort.MyFunction({'ns1:name': 'value'}, function(err, result) { + // request body sent with ` set in the tree structure, you need to set the ignoreBaseNameSpaces to true. This is set because in a lot of workaround the wsdl structure is not correctly +set or the webservice bring errors. + +By default the attribute is set to true. +An example to use: + +A simple `ignoredNamespaces` object, which only adds certain namespaces could look like this: +``` +var options = { +ignoredNamespaces: true +} +``` + +## soap-stub + +Unit testing services that use soap clients can be very cumbersome. In order to get +around this you can use `soap-stub` in conjunction with `sinon` to stub soap with +your clients. + +### Example + +```javascript +// test-initialization-script.js +var sinon = require('sinon'); +var soapStub = require('soap/soap-stub'); + +var urlMyApplicationWillUseWithCreateClient = 'http://path-to-my-wsdl'; +var clientStub = { + SomeOperation: sinon.stub() +}; + +clientStub.SomeOperation.respondWithError = soapStub.createRespondingStub({..error json...}); +clientStub.SomeOperation.respondWithSuccess = soapStub.createRespondingStub({..success json...}); + +soapStub.registerClient('my client alias', urlMyApplicationWillUseWithCreateClient, clientStub); + +// test.js +var soapStub = require('soap/soap-stub'); + +describe('myService', function() { + var clientStub; + var myService; + + beforeEach(function() { + clientStub = soapStub.getStub('my client alias'); + soapStub.reset(); + myService.init(clientStub); + }); + + describe('failures', function() { + beforeEach(function() { + clientStub.SomeOperation.respondWithError(); + }); + + it('should handle error responses', function() { + myService.somethingThatCallsSomeOperation(function(err, response) { + // handle the error response. + }); + }); + }); +}); +``` + + ## Contributors * Author: [Vinay Pulim](https://github.com/vpulim) - * Lead Maintainer: [Joe Spencer](https://github.com/jsdevel) + * Maintainers: + - [Joe Spencer](https://github.com/jsdevel) + - [Heinz Romirer](https://github.com/herom) * [All Contributors](https://github.com/vpulim/node-soap/graphs/contributors) [downloads-image]: http://img.shields.io/npm/dm/soap.svg @@ -451,3 +576,6 @@ namespace prefix is used to identify this Element. This is not much of a problem [travis-url]: https://travis-ci.org/vpulim/node-soap [travis-image]: http://img.shields.io/travis/vpulim/node-soap.svg + +[gitter-url]: https://gitter.im/vpulim/node-soap +[gitter-image]: https://badges.gitter.im/vpulim/node-soap.png diff --git a/lib/client.js b/lib/client.js index 76857fe1b..6382bcda9 100644 --- a/lib/client.js +++ b/lib/client.js @@ -5,18 +5,14 @@ "use strict"; -function findKey(obj, val) { - for (var n in obj) - if (obj[n] === val) - return n; -} var HttpClient = require('./http'), assert = require('assert'), events = require('events'), util = require('util'), debug = require('debug')('node-soap'), - url = require('url'); + findPrefix = require('./utils').findPrefix, + _ = require('lodash'); var Client = function(wsdl, endpoint, options) { events.EventEmitter.call(this); @@ -47,6 +43,22 @@ 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 = []; @@ -98,6 +110,7 @@ Client.prototype._initializeServices = function(endpoint) { Client.prototype._initializeOptions = function(options) { this.wsdl.options.attributesKey = options.attributesKey || 'attributes'; + this.wsdl.options.forceSoap12Headers = !!options.forceSoap12Headers; }; Client.prototype._defineService = function(service, endpoint) { @@ -147,10 +160,16 @@ Client.prototype._invoke = function(method, args, location, callback, options, e xml = null, req = null, soapAction, - alias = findKey(defs.xmlns, ns), + alias = findPrefix(defs.xmlns, ns), headers = { - 'Content-Type': "text/xml; charset=utf-8" - }; + "Content-Type": "text/xml; charset=utf-8" + }, + xmlnsSoap = "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""; + + if (this.wsdl.options.forceSoap12Headers) { + headers["Content-Type"] = "application/soap+xml; charset=utf-8"; + xmlnsSoap = "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\""; + } if (this.SOAPAction) { soapAction = this.SOAPAction; @@ -160,11 +179,14 @@ Client.prototype._invoke = function(method, args, location, callback, options, e soapAction = ((ns.lastIndexOf("/") !== ns.length - 1) ? ns + "/" : ns) + name; } - headers.SOAPAction = '"' + soapAction + '"'; + 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 @@ -173,21 +195,19 @@ Client.prototype._invoke = function(method, args, location, callback, options, e if (self.security && self.security.addOptions) self.security.addOptions(options); - if (input.parts) { + if (input.parts || args === null) { assert.ok(!style || style === 'rpc', 'invalid message definition for document style binding'); message = self.wsdl.objectToRpcXML(name, args, alias, ns); (method.inputSoap === 'encoded') && (encoding = 'soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" '); } else if (typeof (args) === 'string') { message = args; - } else if (args === null) { - message = "<" + name + " />"; } 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 = "' + @@ -210,10 +230,20 @@ Client.prototype._invoke = function(method, args, location, callback, options, e self.lastMessage = message; self.lastRequest = xml; + self.lastEndpoint = location; self.emit('message', message); self.emit('request', xml); + var tryJSONparse = function(body) { + try { + return JSON.parse(body); + } + catch(err) { + return undefined; + } + }; + req = self.httpClient.request(location, xml, function(err, response, body) { var result; var obj; @@ -224,14 +254,19 @@ Client.prototype._invoke = function(method, args, location, callback, options, e if (err) { callback(err); } else { + try { obj = self.wsdl.xmlToObject(body); } catch (error) { - //When the output element cannot be looked up in the wsdl, - //Then instead of sending the error, We pass the body in the response. + // 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.'); - return callback(null,response,body,null); + // If the response is JSON then return it as-is. + var json = _.isObject(body) ? body : tryJSONparse(body); + if (json) { + return callback(null, response, json, null); + } } error.response = response; error.body = body; diff --git a/lib/nscontext.js b/lib/nscontext.js new file mode 100644 index 000000000..c58171f12 --- /dev/null +++ b/lib/nscontext.js @@ -0,0 +1,223 @@ +'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/ClientSSLSecurityPFX.js b/lib/security/ClientSSLSecurityPFX.js new file mode 100644 index 000000000..3ce7af535 --- /dev/null +++ b/lib/security/ClientSSLSecurityPFX.js @@ -0,0 +1,51 @@ +'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/WSSecurity.js b/lib/security/WSSecurity.js index b1bca95b5..d860c08ad 100644 --- a/lib/security/WSSecurity.js +++ b/lib/security/WSSecurity.js @@ -1,12 +1,25 @@ "use strict"; -var crypto = require('crypto') - , passwordDigest = require('../utils').passwordDigest; +var crypto = require('crypto'); +var passwordDigest = require('../utils').passwordDigest; +var validPasswordTypes = ['PasswordDigest', 'PasswordText']; -function WSSecurity(username, password, passwordType) { +function WSSecurity(username, password, options) { + options = options || {}; this._username = username; this._password = password; - this._passwordType = passwordType || 'PasswordText'; + //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'; + } 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; } WSSecurity.prototype.toXML = function() { @@ -24,11 +37,18 @@ WSSecurity.prototype.toXML = function() { } var now = new Date(); var created = getDate(now); - var expires = getDate(new Date(now.getTime() + (1000 * 600))); + var timeStampXml = ''; + if (this._hasTimeStamp) { + var expires = getDate( new Date(now.getTime() + (1000 * 600)) ); + timeStampXml = "" + + ""+created+"" + + ""+expires+"" + + ""; + } var password; if(this._passwordType === 'PasswordText') { - password = "" + this._password + ""; + password = "" + this._password + ""; } else { // nonce = base64 ( sha1 ( created + random ) ) var nHash = crypto.createHash('sha1'); @@ -39,10 +59,7 @@ WSSecurity.prototype.toXML = function() { } return "" + - "" + - "" + created + "" + - "" + expires + "" + - "" + + timeStampXml + "" + "" + this._username + "" + password + diff --git a/lib/security/index.js b/lib/security/index.js index 6b8af894d..d0167d3f1 100644 --- a/lib/security/index.js +++ b/lib/security/index.js @@ -3,6 +3,7 @@ module.exports = { BasicAuthSecurity: require('./BasicAuthSecurity') , ClientSSLSecurity: require('./ClientSSLSecurity') +, ClientSSLSecurityPFX: require('./ClientSSLSecurityPFX') , WSSecurity: require('./WSSecurity') , BearerSecurity: require('./BearerSecurity') }; diff --git a/lib/server.js b/lib/server.js index 1e4ace801..598186ad2 100644 --- a/lib/server.js +++ b/lib/server.js @@ -5,12 +5,6 @@ "use strict"; -function findKey(obj, val) { - for (var n in obj) - if (obj[n] === val) - return n; -} - function getDateString(d) { function pad(n) { return n < 10 ? '0' + n : n; @@ -26,11 +20,12 @@ function getDateString(d) { var url = require('url'), compress = null, events = require('events'), - util = require('util'); + util = require('util'), + findPrefix = require('./utils').findPrefix; try { compress = require("compress"); -} catch (e) { +} catch (error) { } var Server = function(server, path, services, wsdl, options) { @@ -120,7 +115,10 @@ Server.prototype._requestListener = function(req, res) { if (typeof self.log === 'function') { self.log("received", xml); } - self._process(xml, req.url, function(result) { + self._process(xml, req, function(result, statusCode) { + if(statusCode) { + res.statusCode = statusCode; + } res.write(result); res.end(); if (typeof self.log === 'function') { @@ -130,6 +128,7 @@ Server.prototype._requestListener = function(req, res) { } catch (err) { error = err.stack || err; + res.statusCode = 500; res.write(error); res.end(); if (typeof self.log === 'function') { @@ -143,9 +142,9 @@ Server.prototype._requestListener = function(req, res) { } }; -Server.prototype._process = function(input, URL, callback) { +Server.prototype._process = function(input, req, callback) { var self = this, - pathname = url.parse(URL).pathname.replace(/\/$/, ''), + pathname = url.parse(req.url).pathname.replace(/\/$/, ''), obj = this.wsdl.xmlToObject(input), body = obj.Body, headers = obj.Header, @@ -217,7 +216,7 @@ Server.prototype._process = function(input, URL, callback) { args: body[methodName], headers: headers, style: 'rpc' - }, callback); + }, req, callback); } else { var messageElemName = (Object.keys(body)[0] === 'attributes' ? Object.keys(body)[1] : Object.keys(body)[0]); var pair = binding.topElements[messageElemName]; @@ -234,22 +233,19 @@ Server.prototype._process = function(input, URL, callback) { args: body[messageElemName], headers: headers, style: 'document' - }, callback, includeTimestamp); + }, req, callback, includeTimestamp); } } - catch (e) { - if (e.Fault !== undefined) { - // 3rd param is the NS prepended to all elements - // It must match the NS defined in the Envelope (set by the _envelope method) - var fault = self.wsdl.objectToDocumentXML("Fault", e.Fault, "soap"); - callback(self._envelope(fault, includeTimestamp)); + catch (error) { + if (error.Fault !== undefined) { + return self._sendError(error.Fault, callback, includeTimestamp); } - else - throw e; + + throw error; } }; -Server.prototype._executeMethod = function(options, callback, includeTimestamp) { +Server.prototype._executeMethod = function(options, req, callback, includeTimestamp) { options = options || {}; var self = this, method, body, @@ -263,7 +259,7 @@ Server.prototype._executeMethod = function(options, callback, includeTimestamp) try { method = this.services[serviceName][portName][methodName]; - } catch (e) { + } catch (error) { return callback(this._envelope('', includeTimestamp)); } @@ -273,8 +269,7 @@ Server.prototype._executeMethod = function(options, callback, includeTimestamp) handled = true; if (error && error.Fault !== undefined) { - var fault = self.wsdl.objectToDocumentXML("Fault", error.Fault, "soap"); - return callback(self._envelope(fault, includeTimestamp)); + return self._sendError(error.Fault, callback, includeTimestamp); } else if (result === undefined) { // Backward compatibility to support one argument callback style @@ -296,7 +291,7 @@ Server.prototype._executeMethod = function(options, callback, includeTimestamp) callback(''); } - var result = method(args, handleResult, options.headers); + var result = method(args, handleResult, options.headers, req); if (typeof result !== 'undefined') { handleResult(result); } @@ -306,7 +301,7 @@ Server.prototype._envelope = function(body, includeTimestamp) { var defs = this.wsdl.definitions, ns = defs.$targetNamespace, encoding = '', - alias = findKey(defs.xmlns, ns); + alias = findPrefix(defs.xmlns, ns); var xml = "" + " 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 + * 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) { @@ -103,17 +109,11 @@ function deepMerge(destination, source) { }); } -function findKey(obj, val) { - for (var n in obj) - if (obj[n] === val) - return n; -} - var Element = function(nsName, attrs, options) { - var parts = splitNSName(nsName); + var parts = splitQName(nsName); this.nsName = nsName; - this.namespace = parts.namespace; + this.prefix = parts.prefix; this.name = parts.name; this.children = []; this.xmlns = {}; @@ -123,7 +123,7 @@ var Element = function(nsName, attrs, options) { for (var key in attrs) { var match = /^xmlns:?(.*)$/.exec(key); if (match) { - this.xmlns[match[1] ? match[1] : 'xmlns'] = attrs[key]; + this.xmlns[match[1] ? match[1] : TNS_PREFIX] = attrs[key]; } else { if(key === 'value') { @@ -133,6 +133,10 @@ var Element = function(nsName, attrs, options) { } } } + if (this.$targetNamespace !== undefined) { + // Add targetNamespace to the mapping + this.xmlns[TNS_PREFIX] = this.$targetNamespace; + } }; Element.prototype._initializeOptions = function (options) { @@ -151,7 +155,7 @@ 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.namespace; + delete this.prefix; delete this.name; }; @@ -161,7 +165,7 @@ Element.prototype.startElement = function(stack, nsName, attrs, options) { if (!this.allowedChildren) return; - var ChildClass = this.allowedChildren[splitNSName(nsName).name], + var ChildClass = this.allowedChildren[splitQName(nsName).name], element = null; if (ChildClass) { @@ -247,15 +251,15 @@ var ElementTypeMap = { element: [ElementElement, 'annotation complexType'], any: [AnyElement, ''], simpleType: [SimpleTypeElement, 'restriction'], - restriction: [RestrictionElement, 'enumeration choice sequence'], - extension: [ExtensionElement, 'sequence choice'], - choice: [ChoiceElement, 'element choice'], + 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 choice any'], + sequence: [SequenceElement, 'element sequence choice any'], all: [AllElement, 'element choice'], service: [ServiceElement, 'port documentation'], @@ -480,8 +484,8 @@ MessageElement.prototype.postProcess = function(definitions) { delete this.parts; - nsName = splitNSName(part.$element); - ns = nsName.namespace; + nsName = splitQName(part.$element); + ns = nsName.prefix; var schema = definitions.schemas[definitions.xmlns[ns]]; this.element = schema.elements[nsName.name]; if(!this.element) { @@ -523,8 +527,8 @@ MessageElement.prototype.postProcess = function(definitions) { this.element.$lookupTypes = lookupTypes; if (this.element.$type) { - type = splitNSName(this.element.$type); - var typeNs = schema.xmlns && schema.xmlns[type.namespace] || definitions.xmlns[type.namespace]; + type = splitQName(this.element.$type); + var typeNs = schema.xmlns && schema.xmlns[type.prefix] || definitions.xmlns[type.prefix]; if (typeNs) { if (type.name in Primitives) { @@ -559,8 +563,8 @@ MessageElement.prototype.postProcess = function(definitions) { continue; } assert(part.name === 'part', 'Expected part element'); - nsName = splitNSName(part.$type); - ns = definitions.xmlns[nsName.namespace]; + nsName = splitQName(part.$type); + ns = definitions.xmlns[nsName.prefix]; type = nsName.name; var schemaDefinition = definitions.schemas[ns]; if (typeof schemaDefinition !== 'undefined') { @@ -568,12 +572,12 @@ MessageElement.prototype.postProcess = function(definitions) { } else { this.parts[part.$name] = part.$type; } - + if (typeof this.parts[part.$name] === 'object') { - this.parts[part.$name].namespace = nsName.namespace; + this.parts[part.$name].prefix = nsName.prefix; this.parts[part.$name].xmlns = ns; } - + this.children.splice(i--, 1); } } @@ -583,7 +587,7 @@ MessageElement.prototype.postProcess = function(definitions) { /** * 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 `findChildParameterObject`. + * 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"). @@ -592,8 +596,8 @@ MessageElement.prototype.postProcess = function(definitions) { * @private */ MessageElement.prototype._createLookupTypeObject = function (nsString, xmlns) { - var splittedNSString = splitNSName(nsString), - nsAlias = splittedNSString.namespace, + var splittedNSString = splitQName(nsString), + nsAlias = splittedNSString.prefix, splittedName = splittedNSString.name.split('#'), type = splittedName[0], name = splittedName[1], @@ -651,7 +655,7 @@ OperationElement.prototype.postProcess = function(definitions, tag) { children.splice(i--, 1); continue; } - var messageName = splitNSName(child.$message).name; + var messageName = splitQName(child.$message).name; var message = definitions.messages[messageName]; message.postProcess(definitions); if (message.element) { @@ -682,7 +686,7 @@ PortTypeElement.prototype.postProcess = function(definitions) { }; BindingElement.prototype.postProcess = function(definitions) { - var type = splitNSName(this.$type).name, + var type = splitQName(this.$type).name, portType = definitions.portTypes[type], style = this.style, children = this.children; @@ -719,7 +723,7 @@ ServiceElement.prototype.postProcess = function(definitions) { for (var i = 0, child; child = children[i]; i++) { if (child.name !== 'port') continue; - var bindingName = splitNSName(child.$binding).name; + var bindingName = splitQName(child.$binding).name; var binding = bindings[bindingName]; if (binding) { binding.postProcess(definitions); @@ -750,19 +754,19 @@ RestrictionElement.prototype.description = function(definitions, xmlns) { for (var i=0, child; child=children[i]; i++) { if (child instanceof SequenceElement || child instanceof ChoiceElement) { - desc = child.description(definitions); + desc = child.description(definitions, xmlns); break; } } if (desc && this.$base) { - var type = splitNSName(this.$base), + var type = splitQName(this.$base), typeName = type.name, - ns = xmlns && xmlns[type.namespace] || definitions.xmlns[type.namespace], + 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, xmlns); + return typeElement.description(definitions, schema.xmlns); }; return desc; } @@ -780,13 +784,13 @@ ExtensionElement.prototype.description = function(definitions, xmlns) { for (var i=0, child; child=children[i]; i++) { if (child instanceof SequenceElement || child instanceof ChoiceElement) { - desc = child.description(definitions); + desc = child.description(definitions, xmlns); } } if (this.$base) { - var type = splitNSName(this.$base), + var type = splitQName(this.$base), typeName = type.name, - ns = xmlns && xmlns[type.namespace] || definitions.xmlns[type.namespace], + ns = xmlns && xmlns[type.prefix] || definitions.xmlns[type.prefix], schema = definitions.schemas[ns]; if (typeName in Primitives) { @@ -797,7 +801,7 @@ ExtensionElement.prototype.description = function(definitions, xmlns) { schema.types[typeName] || schema.elements[typeName] ); if (typeElement) { - var base = typeElement.description(definitions, xmlns); + var base = typeElement.description(definitions, schema.xmlns); extend(desc, base); } } @@ -852,15 +856,18 @@ ElementElement.prototype.description = function(definitions, xmlns) { name += '[]'; } + if (xmlns && xmlns[TNS_PREFIX]) { + this.$targetNamespace = xmlns[TNS_PREFIX]; + } var type = this.$type || this.$ref; if (type) { - type = splitNSName(type); + type = splitQName(type); var typeName = type.name, - ns = xmlns && xmlns[type.namespace] || definitions.xmlns[type.namespace], + ns = xmlns && xmlns[type.prefix] || definitions.xmlns[type.prefix], schema = definitions.schemas[ns], - typeElement = schema && ( schema.complexTypes[typeName] || schema.types[typeName] || schema.elements[typeName] ); + typeElement = schema && ( this.$type? schema.complexTypes[typeName] || schema.types[typeName] : schema.elements[typeName] ); - if(ns && definitions.schemas[ns]) { + if (ns && definitions.schemas[ns]) { xmlns = definitions.schemas[ns].xmlns; } @@ -886,9 +893,9 @@ ElementElement.prototype.description = function(definitions, xmlns) { else { element[name] = elem; } - + if (typeof elem === 'object') { - elem.targetNSAlias = type.namespace; + elem.targetNSAlias = type.prefix; elem.targetNamespace = ns; } @@ -1074,6 +1081,8 @@ var WSDL = function(definition, uri, options) { WSDL.prototype.ignoredNamespaces = ['tns', 'targetNamespace', 'typedNamespace']; +WSDL.prototype.ignoreBaseNameSpaces = false; + WSDL.prototype.valueKey = '$value'; WSDL.prototype.xmlKey = '$xml'; @@ -1099,6 +1108,16 @@ WSDL.prototype._initializeOptions = function (options) { // Allow any request headers to keep passing through this.options.wsdl_headers = options.wsdl_headers; this.options.wsdl_options = options.wsdl_options; + + 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; + }; WSDL.prototype.onReady = function(callback) { @@ -1114,13 +1133,13 @@ WSDL.prototype._processNextInclude = function(includes, callback) { return callback(); var includePath; - if (!/^http/.test(self.uri) && !/^http/.test(include.location)) { + 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); } - open_wsdl(includePath, this.options, function(err, wsdl) { + open_wsdl_recursive(includePath, this.options, function(err, wsdl) { if (err) { return callback(err); } @@ -1193,7 +1212,7 @@ WSDL.prototype.xmlToObject = function(xml) { var nsName = node.name; var attrs = node.attributes; - var name = splitNSName(nsName).name, + var name = splitQName(nsName).name, attributeName, top = stack[stack.length - 1], topSchema = top.schema, @@ -1253,7 +1272,7 @@ WSDL.prototype.xmlToObject = function(xml) { //Handle element attributes for(attributeName in attrs){ if(/^xmlns:|^xmlns$/.test(attributeName)){ - xmlns[splitNSName(attributeName).name] = attrs[attributeName]; + xmlns[splitQName(attributeName).name] = attrs[attributeName]; continue; } hasNonXmlnsAttribute = true; @@ -1261,8 +1280,8 @@ WSDL.prototype.xmlToObject = function(xml) { } for(attributeName in elementAttributes){ - var res = splitNSName(attributeName); - if(res.name === 'nil' && xmlns[res.namespace] === 'http://www.w3.org/2001/XMLSchema-instance'){ + var res = splitQName(attributeName); + if(res.name === 'nil' && xmlns[res.prefix] === 'http://www.w3.org/2001/XMLSchema-instance'){ hasNilAttribute = true; break; } @@ -1274,8 +1293,15 @@ WSDL.prototype.xmlToObject = function(xml) { var xsiTypeSchema; var xsiType = elementAttributes['xsi:type']; if (xsiType) { - var type = splitNSName(xsiType); - var typeDef = self.findParameterObject(xmlns[type.namespace], type.name); + 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); } @@ -1292,7 +1318,11 @@ WSDL.prototype.xmlToObject = function(xml) { top = stack[stack.length - 1], topObject = top.object, topSchema = top.schema, - name = splitNSName(nsName).name; + 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; @@ -1317,12 +1347,12 @@ WSDL.prototype.xmlToObject = function(xml) { 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); @@ -1342,7 +1372,7 @@ WSDL.prototype.xmlToObject = function(xml) { return; var top = stack[stack.length - 1]; - var name = splitNSName(top.schema).name, + var name = splitQName(top.schema).name, value; if (name === 'int' || name === 'integer') { value = parseInt(text, 10); @@ -1388,7 +1418,10 @@ WSDL.prototype.xmlToObject = function(xml) { if (root.Envelope) { var body = root.Envelope.Body; if (body.Fault) { - var error = new Error(body.Fault.faultcode + ': ' + body.Fault.faultstring + (body.Fault.detail ? ': ' + body.Fault.detail : '')); + 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; } @@ -1397,113 +1430,154 @@ WSDL.prototype.xmlToObject = function(xml) { return root; }; -WSDL.prototype.findParameterObject = function(xmlns, parameterType) { - if (!xmlns || !parameterType) { +/** + * 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 parameterTypeObj = null; + var def = null; if (this.definitions.schemas) { - var schema = this.definitions.schemas[xmlns]; + var schema = this.definitions.schemas[nsURI]; if (schema) { - if (parameterType.indexOf(':') !== -1) { - parameterType = parameterType.substring(parameterType.indexOf(':') + 1, parameterType.length); + 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 `parameterTypeObj` is found in `schema.elements`. - parameterTypeObj = schema.complexTypes[parameterType] || schema.elements[parameterType]; + // the `def` is found in `schema.elements`. + def = schema.complexTypes[qname] || schema.types[qname] || schema.elements[qname]; } } - return parameterTypeObj; + return def; }; -WSDL.prototype.objectToDocumentXML = function(name, params, ns, xmlns, type) { +/** + * 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.findParameterObject(xmlns, type) : null; - if (this.namespaceNumber) { - this.namespaceNumber = 0; - } - return this.objectToXML(args, null, ns, xmlns, true, null, parameterTypeObj); + var parameterTypeObj = type ? this.findSchemaObject(nsURI, type) : null; + return this.objectToXML(args, null, nsPrefix, nsURI, true, null, parameterTypeObj); }; -WSDL.prototype.objectToRpcXML = function(name, params, namespace, xmlns) { - var self = this; +/** + * 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) { var parts = []; var defs = this.definitions; var nsAttrName = '_xmlns'; - namespace = namespace || findKey(defs.xmlns, xmlns); - xmlns = xmlns || defs.xmlns[namespace]; - namespace = namespace === 'xmlns' ? '' : (namespace + ':'); - parts.push(['<', namespace, name, '>'].join('')); + 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]; parts.push(['<', key, '>'].join('')); - parts.push((typeof value === 'object') ? this.objectToXML(value, key, namespace, xmlns) : xmlEscape(value)); + parts.push((typeof value === 'object') ? this.objectToXML(value, key, nsPrefix, nsURI) : xmlEscape(value)); parts.push([''].join('')); } } - parts.push([''].join('')); + parts.push([''].join('')); return parts.join(''); }; -WSDL.prototype.objectToXML = function(obj, name, namespace, xmlns, first, xmlnsAttr, parameterTypeObject, ancestorXmlns) { +/** + * 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[xmlns]; + var schema = this.definitions.schemas[nsURI]; - var parentNamespace = namespace ? namespace.parent : undefined; - if(typeof parentNamespace !== 'undefined') { - //we got the parentNamespace for our array. setting the namespace-variable back to the current namespace string - namespace = namespace.current; + 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; } var soapHeader = !schema; var qualified = schema && schema.$elementFormDefault === 'qualified'; var parts = []; - var prefixNamespace = (namespace || qualified) && namespace !== 'xmlns'; + var prefixNamespace = (nsPrefix || qualified) && nsPrefix !== TNS_PREFIX; var xmlnsAttrib = ''; - if (xmlns && first) { + if (nsURI && isFirst) { - if (prefixNamespace && this.options.ignoredNamespaces.indexOf(namespace) === -1) { + if (prefixNamespace && this.options.ignoredNamespaces.indexOf(nsPrefix) === -1) { // resolve the prefix namespace - xmlnsAttrib += ' xmlns:' + namespace + '="' + xmlns + '"'; + xmlnsAttrib += ' xmlns:' + nsPrefix + '="' + nsURI + '"'; } // only add default namespace if the schema elementFormDefault is qualified - if (qualified || soapHeader) xmlnsAttrib += ' xmlns="' + xmlns + '"'; + if (qualified || soapHeader) xmlnsAttrib += ' xmlns="' + nsURI + '"'; + } + + if (!nsContext) { + nsContext = new NamespaceContext(); + nsContext.declareNamespace(nsPrefix, nsURI); + } else { + nsContext.pushContext(); } - var ancXmlns = ancestorXmlns ? ancestorXmlns : new Array(xmlns); - // explicitly use xmlns attribute if available if (xmlnsAttr) { xmlnsAttrib = xmlnsAttr; } var ns = ''; - if (prefixNamespace && ((qualified || first) || soapHeader) && this.options.ignoredNamespaces.indexOf(namespace) === -1) { + if (prefixNamespace && ((qualified || isFirst) || soapHeader) && this.options.ignoredNamespaces.indexOf(nsPrefix) === -1) { // prefix element - ns = namespace.indexOf(":") === -1 ? namespace + ':' : namespace; + ns = nsPrefix.indexOf(":") === -1 ? nsPrefix + ':' : nsPrefix; } + var i, n; + // start building out XML string. if (Array.isArray(obj)) { - for (var i = 0, item; item = obj[i]; i++) { - var arrayAttr = self.processAttributes(item), - correctOuterNamespace = parentNamespace || ns; //using the parent namespace if given + 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(['<', correctOuterNamespace, name, arrayAttr, xmlnsAttrib, '>'].join('')); - parts.push(self.objectToXML(item, name, namespace, xmlns, false, null, parameterTypeObject, ancXmlns)); - parts.push([''].join('')); + parts.push(['<', correctOuterNsPrefix, name, arrayAttr, xmlnsAttrib, '>'].join('')); + parts.push(self.objectToXML(item, name, nsPrefix, nsURI, false, null, schemaObject, nsContext)); + 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; @@ -1521,75 +1595,142 @@ WSDL.prototype.objectToXML = function(obj, name, namespace, xmlns, first, xmlnsA if (typeof child === 'undefined') continue; - var attr = self.processAttributes(child); + var attr = self.processAttributes(child, nsContext); var value = ''; var nonSubNameSpace = ''; - if (first) { - value = self.objectToXML(child, name, namespace, xmlns, false, null, parameterTypeObject, ancXmlns); + + var nameWithNsRegex = /^([^:]+):([^:]+)$/.exec(name); + if (nameWithNsRegex) { + nonSubNameSpace = nameWithNsRegex[1] + ':'; + name = nameWithNsRegex[2]; + } + + if (isFirst) { + value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, schemaObject, nsContext); } else { if (self.definitions.schemas) { if (schema) { - var childParameterTypeObject = self.findChildParameterObject(parameterTypeObject, name); + var childSchemaObject = self.findChildSchemaObject(schemaObject, name); //find sub namespace if not a primitive - if (childParameterTypeObject && childParameterTypeObject.$type && (childParameterTypeObject.$type.indexOf('xsd') === -1)) { - if(childParameterTypeObject.$baseNameSpace) { //this element has a base with another namespace (the correct one) - ns = childParameterTypeObject.$baseNameSpace + ':'; - } - - var childParameterType = childParameterTypeObject.$type; - - var childNamespace = ''; + 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 = ''; - if (childParameterType.indexOf(':') !== -1) { - childNamespace = childParameterType.substring(0, childParameterType.indexOf(':')); - childName = childParameterType.substring(childParameterType.indexOf(':') + 1); - } - var childXmlns = schema.xmlns[childNamespace] || self.definitions.xmlns[childNamespace]; + var childNsURI; var childXmlnsAttrib = ''; - if ((ancXmlns.indexOf(childXmlns) === -1) && (childXmlns)) { - childXmlnsAttrib = ' xmlns:' + childNamespace + '="' + childXmlns+ '"'; - if ((childXmlns)) { - ancXmlns.push(childXmlns); + + 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); + for (i = 0, n = this.ignoredNamespaces.length; i < n; i++) { + if (this.ignoredNamespaces[i] === childNsPrefix) { + childNsPrefix = nsPrefix; + break; + } + } + } else { + childNsPrefix = elementQName.prefix; + for (i = 0, n = this.ignoredNamespaces.length; i < n; i++) { + if (this.ignoredNamespaces[i] === childNsPrefix) { + childNsPrefix = nsPrefix; + break; + } + } + 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 === 'unqualified'; + } + } + if (unqualified) { + childNsPrefix = ''; } - } - var completeChildParameterTypeObject = self.findChildParameterObjectFromSchema(childName, childXmlns) || childParameterTypeObject; - for(var ignoredNamespacesIndex = 0, ignoredNamespacesLength = this.ignoredNamespaces.length; ignoredNamespacesIndex < ignoredNamespacesLength; ignoredNamespacesIndex++) { - if(this.ignoredNamespaces[ignoredNamespacesIndex] === childNamespace) { - childNamespace = namespace; + if (childNsURI && childNsPrefix) { + if (nsContext.declareNamespace(childNsPrefix, childNsURI)) { + childXmlnsAttrib = ' xmlns:' + childNsPrefix + '="' + childNsURI + '"'; + xmlnsAttrib += childXmlnsAttrib; + } + } + } - break; + 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') { + // Add the prefix/namespace mapping, but not declare it + nsContext.addNamespace(typeQName.prefix, 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; + } + + ns = childNsPrefix ? childNsPrefix + ':' : ''; + if(Array.isArray(child)) { //for arrays, we need to remember the current namespace - childNamespace = { - current: childNamespace, + childNsPrefix = { + current: childNsPrefix, parent: ns }; } - value = self.objectToXML(child, name, childNamespace, childXmlns, false, childXmlnsAttrib, completeChildParameterTypeObject, ancXmlns); - } 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.findChildParameterObjectFromSchema(obj[self.options.attributesKey].xsi_type.type, obj[self.options.attributesKey].xsi_type.xmlns); - - nonSubNameSpace = obj[self.options.attributesKey].xsi_type.namespace + ':'; - ancXmlns.push(obj[self.options.attributesKey].xsi_type.xmlns); - value = self.objectToXML(child, name, obj[self.options.attributesKey].xsi_type.namespace, obj[self.options.attributesKey].xsi_type.xmlns, false, null, null, ancXmlns); + value = self.objectToXML(child, name, childNsPrefix, childNsURI, + false, null, 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 { - value = self.objectToXML(child, name, namespace, xmlns, false, null, null, ancXmlns); + value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, null, nsContext); } } else { - value = self.objectToXML(child, name, namespace, xmlns, false, null, null, ancXmlns); + value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, null, nsContext); } } } if (!Array.isArray(child)) { - parts.push(['<', nonSubNameSpace || ns, name, attr, xmlnsAttrib, (child === null ? ' xsi:nil="true"' : ''), '>'].join('')); + parts.push(['<', nonSubNameSpace || ns, name, attr, xmlnsAttrib, + (child === null ? ' xsi:nil="true"' : ''), '>'].join('')); } parts.push(value); @@ -1600,42 +1741,43 @@ WSDL.prototype.objectToXML = function(obj, name, namespace, xmlns, first, xmlnsA } else if (obj !== undefined) { parts.push(xmlEscape(obj)); } + nsContext.popContext(); return parts.join(''); }; -WSDL.prototype.processAttributes = function(child) { - var self = this; +WSDL.prototype.processAttributes = function(child, nsContext) { var attr = ''; if(child === null) { child = []; } - if (child[this.options.attributesKey] && child[this.options.attributesKey].xsi_type) { - var xsiType = child[this.options.attributesKey].xsi_type; + 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 (!xsiType.namespace) { - if (self.namespaceNumber) { - self.namespaceNumber++; - } else { - self.namespaceNumber = 1; - } - xsiType.namespace = 'ns' + self.namespaceNumber; + if (!prefix) { + prefix = nsContext.registerNamespace(xsiType.xmlns); + } else { + nsContext.declareNamespace(prefix, xsiType.xmlns); } + xsiType.prefix = prefix; } - if (child[this.options.attributesKey]) { - for (var attrKey in child[this.options.attributesKey]) { + + if (attrObj) { + for (var attrKey in attrObj) { //handle complex extension separately if (attrKey === 'xsi_type') { - var attrValue = child[this.options.attributesKey][attrKey]; - attr += ' xsi:type="' + attrValue.namespace + ':' + attrValue.type + '"'; - attr += ' xmlns:' + attrValue.namespace + '="' + attrValue.xmlns + '"'; + var attrValue = attrObj[attrKey]; + attr += ' xsi:type="' + attrValue.prefix + ':' + attrValue.type + '"'; + attr += ' xmlns:' + attrValue.prefix + '="' + attrValue.xmlns + '"'; continue; } else { - attr += ' ' + attrKey + '="' + xmlEscape(child[this.options.attributesKey][attrKey]) + '"'; + attr += ' ' + attrKey + '="' + xmlEscape(attrObj[attrKey]) + '"'; } } } @@ -1643,12 +1785,18 @@ WSDL.prototype.processAttributes = function(child) { return attr; }; -WSDL.prototype.findChildParameterObjectFromSchema = function(name, xmlns) { - if (!this.definitions.schemas || !name || !xmlns) { +/** + * 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[xmlns]; + var schema = this.definitions.schemas[nsURI]; if (!schema || !schema.complexTypes) { return null; } @@ -1656,15 +1804,16 @@ WSDL.prototype.findChildParameterObjectFromSchema = function(name, xmlns) { return schema.complexTypes[name]; }; -WSDL.prototype.findChildParameterObject = function(parameterTypeObj, childName) { +WSDL.prototype.findChildSchemaObject = function(parameterTypeObj, childName) { if (!parameterTypeObj || !childName) { return null; } var found = null, i = 0, - child; + child, + ref; - if(parameterTypeObj.$lookupTypes && Array.isArray(parameterTypeObj.$lookupTypes && parameterTypeObj.$lookupTypes.length)) { + if(Array.isArray(parameterTypeObj.$lookupTypes) && parameterTypeObj.$lookupTypes.length) { var types = parameterTypeObj.$lookupTypes; for(i = 0; i < types.length; i++) { @@ -1675,37 +1824,59 @@ WSDL.prototype.findChildParameterObject = function(parameterTypeObj, childName) break; } } - } else { - var object = parameterTypeObj; - if (object.$name === childName) { + } + + var object = parameterTypeObj; + if (object.$name === childName) { + return object; + } + if (object.$ref) { + ref = splitQName(object.$ref); + if (ref.name === childName) { return object; } + } - if (object.children) { - for (i = 0, child; child = object.children[i]; i++) { - found = this.findChildParameterObject(child, childName); - if (found) { - break; - } + var childNsURI; + if (object.$type) { + 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); + } + } - if(child.$base) { - var childNameSpace = child.$base.substr(0, child.$base.indexOf(':')), - childXmlns = this.definitions.xmlns[childNameSpace]; + if (object.children) { + for (i = 0, child; child = object.children[i]; i++) { + found = this.findChildSchemaObject(child, childName); + if (found) { + break; + } - var foundBase = this.findChildParameterObjectFromSchema(child.$base.substr(child.$base.indexOf(':') + 1), childXmlns); + if (child.$base) { + var baseQName = splitQName(child.$base); + var childNameSpace = baseQName.prefix === TNS_PREFIX ? '' : baseQName.prefix; + childNsURI = this.definitions.xmlns[baseQName.prefix]; - if (foundBase) { - found = this.findChildParameterObject(foundBase, childName); + var foundBase = this.findSchemaType(baseQName.name, childNsURI); - if (found) { - found.$baseNameSpace = childNameSpace; - found.$type = childNameSpace + ':' + childName; - break; - } + if (foundBase) { + found = this.findChildSchemaObject(foundBase, childName); + + if (found) { + found.$baseNameSpace = childNameSpace; + found.$type = childNameSpace + ':' + childName; + break; } } } } + } return found; @@ -1737,7 +1908,7 @@ WSDL.prototype._parse = function(xml) { } } } else { - name = splitNSName(nsName).name; + name = splitQName(nsName).name; if (name === 'definitions') { root = new DefinitionsElement(nsName, attrs, options); stack.push(root); @@ -1785,7 +1956,7 @@ WSDL.prototype._xmlnsMap = function() { var xmlns = this.definitions.xmlns; var str = ''; for (var alias in xmlns) { - if (alias === '' || alias === 'xmlns') + if (alias === '' || alias === TNS_PREFIX) continue; var ns = xmlns[alias]; switch (ns) { @@ -1808,6 +1979,41 @@ WSDL.prototype._xmlnsMap = function() { return str; }; +var WSDL_CACHE = { }; + +/* + * 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; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + 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; @@ -1818,7 +2024,7 @@ function open_wsdl(uri, options, callback) { var request_options = options.wsdl_options; var wsdl; - if (!/^http/.test(uri)) { + if (!/^https?/.test(uri)) { debug('Reading file: %s', uri); fs.readFile(uri, 'utf8', function(err, definition) { if (err) { @@ -1826,18 +2032,20 @@ function open_wsdl(uri, options, callback) { } else { wsdl = new WSDL(definition, uri, options); + WSDL_CACHE[ uri ] = wsdl; wsdl.onReady(callback); } }); } else { debug('Reading url: %s', uri); - var httpClient = new HttpClient(options); + 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.onReady(callback); } else { callback(new Error('Invalid WSDL URL: ' + uri + "\n\n\r Code: " + response.statusCode + "\n\n\r Response Body: " + response.body)); diff --git a/package.json b/package.json index ab312124c..2807e17cf 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,18 @@ { "name": "soap", - "version": "0.9.3", + "version": "0.11.0", "description": "A minimal node SOAP client", "engines": { "node": ">=0.8.0" }, "author": "Vinay Pulim ", "dependencies": { + "debug": "~0.7.4", "lodash": "~2.4.1", "request": ">=2.9.0", "sax": ">=0.6", - "strip-bom": "~0.3.1", - "debug": "~0.7.4" + "selectn": "^0.9.6", + "strip-bom": "~0.3.1" }, "repository": { "type": "git", @@ -23,7 +24,7 @@ }, "scripts": { "pretest": "jshint index.js lib test", - "test": "mocha test/*-test.js test/security/*.js" + "test": "mocha --timeout 10000 test/*-test.js test/security/*.js" }, "keywords": [ "soap" @@ -34,7 +35,10 @@ "jshint": "2.3.0", "glob": "~3.2.8", "should": "~3.3.0", - "timekeeper": "~0.0.4" + "timekeeper": "~0.0.4", + "duplexer": "~0.1.1", + "readable-stream": "~2.0.2", + "semver": "~5.0.3" }, "publishConfig": { "registry": "https://artifactory.secureserver.net/artifactory/api/npm/node-platformapi-local/" diff --git a/soap-stub.js b/soap-stub.js new file mode 100644 index 000000000..36000c133 --- /dev/null +++ b/soap-stub.js @@ -0,0 +1,148 @@ +var _ = require('lodash'); + +var aliasedClientStubs = {}; +var clientStubs = {}; + +/** + * This module stubs the soap module to allow for offline + * testing and stubbed clients. All clients' methods are stubbed with sinon and have + * additional functionality: + * + *
    + *
  • .respondWithError() - Responds with the mocked client's sample error response.
  • + *
  • .respondWithSuccess() - Responds with the mocked client's sample success response.
  • + *
+ * + * Register a client by calling `.registerClient(urlToWsdl, clientStub)`. For an + * example client stub, see ./soap-stub-client-example.js. + * + * @property {Boolean} errOnCreateClient returns an error to the createClient method when set to true. + */ +module.exports = { + createClient: createClient, + createErroringStub: createErroringStub, + createRespondingStub: createRespondingStub, + errOnCreateClient: false, + getStub: getStub, + registerClient: registerClient, + reset: reset, + security: require('./lib/security') +}; + +/** + * Return a stubbed client based on the value of wsdlUrl. + * + * @throws if wsdlUrl is unknown. + * + * @param {String} wsdlUrl + * @param {Object} options + * @param {Function} cb + * @return {Object} + */ +function createClient(wsdlUrl, options, cb) { + if (!cb) { + cb = options; + options = {}; + } + + if (this.errOnCreateClient) { + return setTimeout(cb.bind(null, new Error('forced error on createClient'))); + } + + var client = getStub(wsdlUrl); + + if (client) { + resetStubbedMethods(client); + setTimeout(cb.bind(null, null, client)); + } else { + setTimeout(cb.bind(null, new Error('no client stubbed for ' + wsdlUrl))); + } +} + +/** + * Returns a method that calls all callbacks given to the method it is attached + * to with the given error. + * + *
+ * myClientStub.someMethod.errorOnCall = createErroringStub(error);
+ *
+ * // elsewhere
+ *
+ * myClientStub.someMethod.errorOnCall();
+ * 
+ * + * @param {?} object anything + * @return {Function} + */ +function createErroringStub(err) { + return function() { + this.args.forEach(function(argSet) { + setTimeout(argSet[1].bind(null, err)); + }); + this.yields(err); + }; +} + +/** + * Returns a method that calls all callbacks given to the method it is attached + * to with the given response. + * + *
+ * myClientStub.someMethod.respondWithError = createRespondingStub(errorResponse);
+ *
+ * // elsewhere
+ *
+ * myClientStub.someMethod.respondWithError();
+ * 
+ * + * @param {?} object anything + * @return {Function} + */ +function createRespondingStub(object) { + return function() { + this.args.forEach(function(argSet) { + setTimeout(argSet[1].bind(null, null, object)); + }); + this.yields(null, object); + }; +} + +/** + * Registers a stubbed client with soap-stub. urlToWsdl is the path you will use + * in your app. + * + * @param {String} alias A simple name to refer to the clientStub as. + * @param {String} urlToWsdl May be file system URL or http URL. + * @param {Object} clientStub A client with stubbed methods. + */ +function registerClient(alias, urlToWsdl, clientStub) { + aliasedClientStubs[alias] = clientStub; + clientStubs[urlToWsdl] = clientStub; +} + +/** + * Resets state associated with clientStubs. + */ +function reset() { + _.forEach(clientStubs, resetStubbedMethods); + this.errOnCreateClient = false; +} + +/** + * Returns a previously registered client stub. + * + * @param {String} aliasOrWsdlUrl + * @return {Object} clientStub + */ +function getStub(aliasOrWsdlUrl) { + return aliasedClientStubs[aliasOrWsdlUrl] || clientStubs[aliasOrWsdlUrl]; +} + +function resetStubbedMethods(client) { + Object.keys(client).forEach(function(method) { + method = client[method]; + if (typeof method === 'function' && typeof method.reset === 'function') { + method.reset(); + } + }); +} diff --git a/test/certs/client-password.pfx b/test/certs/client-password.pfx new file mode 100644 index 000000000..c3d8ae13e Binary files /dev/null and b/test/certs/client-password.pfx differ diff --git a/test/certs/pfk-buffer.pfx b/test/certs/pfk-buffer.pfx new file mode 100644 index 000000000..561366cec Binary files /dev/null and b/test/certs/pfk-buffer.pfx differ diff --git a/test/certs/server-password.pfx b/test/certs/server-password.pfx new file mode 100644 index 000000000..81d2c5070 Binary files /dev/null and b/test/certs/server-password.pfx differ diff --git a/test/client-customHttp-test.js b/test/client-customHttp-test.js new file mode 100644 index 000000000..81df60e5d --- /dev/null +++ b/test/client-customHttp-test.js @@ -0,0 +1,111 @@ +'use strict'; + +var fs = require('fs'), + soap = require('..'), + http = require('http'), + assert = require('assert'), + duplexer = require('duplexer'), + req = require('request'), + httpClient = require('../lib/http.js'), + // stream = require('stream'), + stream = require('readable-stream'), + util = require('util'), + events = require('events'), + semver = require('semver'), + should = require('should'); + +it('should allow customization of httpClient and the wsdl file download should pass through it', function(done) { + +//Make a custom http agent to use streams instead on net socket + function CustomAgent(options, socket){ + var self = this; + events.EventEmitter.call(this); + self.requests = []; + self.maxSockets = 1; + self.proxyStream = socket; + self.options = options || {}; + self.proxyOptions = {}; + } + + util.inherits(CustomAgent, events.EventEmitter); + + CustomAgent.prototype.addRequest = function(req, options) { + req.onSocket(this.proxyStream); + }; + + //Create a duplex stream + + var httpReqStream = new stream.PassThrough(); + var httpResStream = new stream.PassThrough(); + var socketStream = duplexer(httpReqStream, httpResStream); + + + //Custom httpClient + function MyHttpClient (options, socket){ + httpClient.call(this,options); + this.agent = new CustomAgent(options, socket); + } + + util.inherits(MyHttpClient, httpClient); + + MyHttpClient.prototype.request = function(rurl, data, callback, exheaders, exoptions) { + var self = this; + var options = self.buildRequest(rurl, data, exheaders, exoptions); + //Specify agent to use + options.agent = this.agent; + var headers = options.headers; + var req = self._request(options, function(err, res, body) { + if (err) { + return callback(err); + } + body = self.handleResponse(req, res, body); + callback(null, res, body); + }); + if (headers.Connection !== 'keep-alive') { + req.end(data); + } + return req; + }; + + var wsdl = fs.readFileSync('./test/wsdl/default_namespace.wsdl').toString('utf8'); + //Should be able to read from stream the request + httpReqStream.once('readable', function readRequest() { + var chunk = httpReqStream.read(); + should.exist(chunk); + + //This is for compatibility with old node releases <= 0.10 + //Hackish + if(semver.lt(process.version, '0.11.0')) + { + socketStream.on('data', function(data) { + socketStream.ondata(data,0,1984); + }); + } + //Now write the response with the wsdl + var state = httpResStream.write('HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: 1904\r\n\r\n'+wsdl); + }); + + var httpCustomClient = new MyHttpClient({}, socketStream); + var url = 'http://localhost:50000/Platform.asmx?wsdl'; + soap.createClient(url, + {httpClient: httpCustomClient}, + function(err, client) { + assert.ok(client); + assert.ok(!err); + assert.equal(client.httpClient, httpCustomClient); + var description = (client.describe()); + assert.deepEqual(client.describe(), { + MyService: { + MyServicePort: { + MyOperation: { + input: { + }, + output: { + } + } + } + } + }); + done(); + }); +}); \ No newline at end of file diff --git a/test/client-test.js b/test/client-test.js index d4c88692b..bf54df31c 100644 --- a/test/client-test.js +++ b/test/client-test.js @@ -210,6 +210,23 @@ describe('SOAP Client', function() { }, null, {"test-header": 'test'}); }, baseUrl); }); + + it('should add proper headers for soap12', function(done) { + soap.createClient(__dirname+'/wsdl/default_namespace_soap12.wsdl', {forceSoap12Headers: true}, function(err, client) { + assert.ok(client); + assert.ok(!err); + + client.MyOperation({}, function(err, result) { + assert.ok(result); + assert.ok(client.lastRequestHeaders); + assert.ok(client.lastRequest); + assert.equal(client.lastRequestHeaders['Content-Type'], 'application/soap+xml; charset=utf-8'); + assert.notEqual(client.lastRequest.indexOf('xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\"'), -1); + assert( !client.lastRequestHeaders.SOAPAction ); + done(); + }, null, {'test-header': 'test'}); + }, baseUrl); + }); }); it('should add soap headers', function (done) { @@ -250,6 +267,22 @@ describe('SOAP Client', function() { done(); }); }); + + it('should add http headers', function(done) { + soap.createClient(__dirname+'/wsdl/default_namespace.wsdl', function(err, client) { + assert.ok(client); + assert.ok(!client.getHttpHeaders()); + + client.addHttpHeader('foo', 'bar'); + + assert.ok(client.getHttpHeaders()); + assert.equal(client.getHttpHeaders().foo, 'bar'); + + client.clearHttpHeaders(); + assert.equal(Object.keys(client.getHttpHeaders()).length, 0); + done(); + }); + }); describe('Namespace number', function() { var server = null; @@ -288,12 +321,14 @@ describe('SOAP Client', function() { client.MyOperation(data, function(err, result) { assert.ok(client.lastRequest); assert.ok(client.lastMessage); + assert.ok(client.lastEndpoint); assert.equal(client.lastMessage, message); delete data.attributes.xsi_type.namespace; client.MyOperation(data, function(err, result) { assert.ok(client.lastRequest); assert.ok(client.lastMessage); + assert.ok(client.lastEndpoint); assert.equal(client.lastMessage, message); done(); @@ -514,4 +549,34 @@ describe('SOAP Client', function() { }); }); + + it('should return error in the call when Fault was returned', function(done) { + var server = null; + var hostname = '127.0.0.1'; + var port = 15099; + var baseUrl = 'http://' + hostname + ':' + port; + + server = http.createServer(function (req, res) { + res.statusCode = 200; + res.write("\nTesttest errortest detail"); + res.end(); + }).listen(port, hostname, function() { + soap.createClient(__dirname+'/wsdl/json_response.wsdl', function(err, client) { + assert.ok(client); + assert.ok(!err); + + client.MyOperation({}, function(err, result, body) { + server.close(); + server = null; + assert.ok(err); + assert.strictEqual(err.message, 'Test: test error: test detail'); + assert.ok(result); + assert.ok(body); + done(); + }); + }, baseUrl); + }); + + }); + }); diff --git a/test/request-response-samples-test.js b/test/request-response-samples-test.js index d847f98ae..2a81b2cb1 100644 --- a/test/request-response-samples-test.js +++ b/test/request-response-samples-test.js @@ -23,7 +23,8 @@ var requestContext = { requestHandler:function(req, res){ var chunks = []; req.on('data', function(chunk){ - chunks.push(chunk); + // ignore eol on sample files. + chunks.push(chunk.toString().replace(/\r?\n$/m, '')); }); req.on('end', function(){ if(!requestContext.expectedRequest)return res.end(requestContext.responseToSend); @@ -104,11 +105,12 @@ function generateTest(name, methodName, wsdlPath, headerJSON, securityJSON, requ } } if (securityJSON && securityJSON.type === 'ws') { - client.setSecurity(new WSSecurity(securityJSON.username, securityJSON.password)); + client.setSecurity(new WSSecurity(securityJSON.username, securityJSON.password, securityJSON.options)); } client[methodName](requestJSON, function(err, json, body, soapHeader){ if(requestJSON){ if (err) { + assert.notEqual('undefined: undefined', err.message); assert.deepEqual(err.root, responseJSON); } else { // assert.deepEqual(json, responseJSON); diff --git a/test/request-response-samples/Dummy__should_add_prefix_for_unqualified_global_elements/request.json b/test/request-response-samples/Dummy__should_add_prefix_for_unqualified_global_elements/request.json index a73e2e8ca..21af9df45 100644 --- a/test/request-response-samples/Dummy__should_add_prefix_for_unqualified_global_elements/request.json +++ b/test/request-response-samples/Dummy__should_add_prefix_for_unqualified_global_elements/request.json @@ -6,6 +6,6 @@ } }, "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" } } diff --git a/test/request-response-samples/Dummy__should_add_prefix_for_unqualified_global_elements/request.xml b/test/request-response-samples/Dummy__should_add_prefix_for_unqualified_global_elements/request.xml index 5d901d852..04a5ce950 100644 --- a/test/request-response-samples/Dummy__should_add_prefix_for_unqualified_global_elements/request.xml +++ b/test/request-response-samples/Dummy__should_add_prefix_for_unqualified_global_elements/request.xml @@ -1 +1 @@ -HumptyDumpty \ No newline at end of file +HumptyDumpty \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_extract_envelope_from_response/request.json b/test/request-response-samples/Dummy__should_extract_envelope_from_response/request.json index 1f3a2941d..52d6f57f7 100644 --- a/test/request-response-samples/Dummy__should_extract_envelope_from_response/request.json +++ b/test/request-response-samples/Dummy__should_extract_envelope_from_response/request.json @@ -1,6 +1,6 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" } -} \ No newline at end of file +} diff --git a/test/request-response-samples/Dummy__should_extract_envelope_from_response/request.xml b/test/request-response-samples/Dummy__should_extract_envelope_from_response/request.xml index c28cceff9..e377aaa35 100644 --- a/test/request-response-samples/Dummy__should_extract_envelope_from_response/request.xml +++ b/test/request-response-samples/Dummy__should_extract_envelope_from_response/request.xml @@ -1 +1 @@ -HumptyDumpty \ No newline at end of file +HumptyDumpty \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_handle_attributes_in_request/request.json b/test/request-response-samples/Dummy__should_handle_attributes_in_request/request.json index a73e2e8ca..21af9df45 100644 --- a/test/request-response-samples/Dummy__should_handle_attributes_in_request/request.json +++ b/test/request-response-samples/Dummy__should_handle_attributes_in_request/request.json @@ -6,6 +6,6 @@ } }, "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" } } diff --git a/test/request-response-samples/Dummy__should_handle_attributes_in_request/request.xml b/test/request-response-samples/Dummy__should_handle_attributes_in_request/request.xml index 5c351cf7e..e3d0981b0 100644 --- a/test/request-response-samples/Dummy__should_handle_attributes_in_request/request.xml +++ b/test/request-response-samples/Dummy__should_handle_attributes_in_request/request.xml @@ -1 +1 @@ -HumptyDumpty \ No newline at end of file +HumptyDumpty \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_handle_attributes_in_response/request.json b/test/request-response-samples/Dummy__should_handle_attributes_in_response/request.json index 1f3a2941d..52d6f57f7 100644 --- a/test/request-response-samples/Dummy__should_handle_attributes_in_response/request.json +++ b/test/request-response-samples/Dummy__should_handle_attributes_in_response/request.json @@ -1,6 +1,6 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" } -} \ No newline at end of file +} diff --git a/test/request-response-samples/Dummy__should_handle_attributes_in_response/request.xml b/test/request-response-samples/Dummy__should_handle_attributes_in_response/request.xml index c28cceff9..e377aaa35 100644 --- a/test/request-response-samples/Dummy__should_handle_attributes_in_response/request.xml +++ b/test/request-response-samples/Dummy__should_handle_attributes_in_response/request.xml @@ -1 +1 @@ -HumptyDumpty \ No newline at end of file +HumptyDumpty \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_handle_cdata_xml_in_response/request.json b/test/request-response-samples/Dummy__should_handle_cdata_xml_in_response/request.json index 1f3a2941d..52d6f57f7 100644 --- a/test/request-response-samples/Dummy__should_handle_cdata_xml_in_response/request.json +++ b/test/request-response-samples/Dummy__should_handle_cdata_xml_in_response/request.json @@ -1,6 +1,6 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" } -} \ No newline at end of file +} diff --git a/test/request-response-samples/Dummy__should_handle_cdata_xml_in_response/request.xml b/test/request-response-samples/Dummy__should_handle_cdata_xml_in_response/request.xml index c28cceff9..e377aaa35 100644 --- a/test/request-response-samples/Dummy__should_handle_cdata_xml_in_response/request.xml +++ b/test/request-response-samples/Dummy__should_handle_cdata_xml_in_response/request.xml @@ -1 +1 @@ -HumptyDumpty \ No newline at end of file +HumptyDumpty \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_handle_nil/request.json b/test/request-response-samples/Dummy__should_handle_nil/request.json index 1f3a2941d..52d6f57f7 100644 --- a/test/request-response-samples/Dummy__should_handle_nil/request.json +++ b/test/request-response-samples/Dummy__should_handle_nil/request.json @@ -1,6 +1,6 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" } -} \ No newline at end of file +} diff --git a/test/request-response-samples/Dummy__should_handle_nil/request.xml b/test/request-response-samples/Dummy__should_handle_nil/request.xml index c28cceff9..e377aaa35 100644 --- a/test/request-response-samples/Dummy__should_handle_nil/request.xml +++ b/test/request-response-samples/Dummy__should_handle_nil/request.xml @@ -1 +1 @@ -HumptyDumpty \ No newline at end of file +HumptyDumpty \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_ignore_custom_defined_namespaces/request.json b/test/request-response-samples/Dummy__should_ignore_custom_defined_namespaces/request.json index 5d29a0b5d..1a89d6b8a 100644 --- a/test/request-response-samples/Dummy__should_ignore_custom_defined_namespaces/request.json +++ b/test/request-response-samples/Dummy__should_ignore_custom_defined_namespaces/request.json @@ -1,11 +1,11 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" }, "DummyAccount": { "DummyName": "Dummy", "DummyPassword": "1234" } -} \ No newline at end of file +} diff --git a/test/request-response-samples/Dummy__should_ignore_custom_defined_namespaces/request.xml b/test/request-response-samples/Dummy__should_ignore_custom_defined_namespaces/request.xml index b9c5a2b8b..2bb3fb2e8 100644 --- a/test/request-response-samples/Dummy__should_ignore_custom_defined_namespaces/request.xml +++ b/test/request-response-samples/Dummy__should_ignore_custom_defined_namespaces/request.xml @@ -1 +1 @@ -HumptyDumptyDummy1234 \ No newline at end of file +HumptyDumptyDummy1234 \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_ignore_defined_namespaces/request.json b/test/request-response-samples/Dummy__should_ignore_defined_namespaces/request.json index 5d29a0b5d..1a89d6b8a 100644 --- a/test/request-response-samples/Dummy__should_ignore_defined_namespaces/request.json +++ b/test/request-response-samples/Dummy__should_ignore_defined_namespaces/request.json @@ -1,11 +1,11 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" }, "DummyAccount": { "DummyName": "Dummy", "DummyPassword": "1234" } -} \ No newline at end of file +} diff --git a/test/request-response-samples/Dummy__should_ignore_defined_namespaces/request.xml b/test/request-response-samples/Dummy__should_ignore_defined_namespaces/request.xml index b9c5a2b8b..2bb3fb2e8 100644 --- a/test/request-response-samples/Dummy__should_ignore_defined_namespaces/request.xml +++ b/test/request-response-samples/Dummy__should_ignore_defined_namespaces/request.xml @@ -1 +1 @@ -HumptyDumptyDummy1234 \ No newline at end of file +HumptyDumptyDummy1234 \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_parse_nil/request.json b/test/request-response-samples/Dummy__should_parse_nil/request.json index 85fc023a8..1b52c5098 100644 --- a/test/request-response-samples/Dummy__should_parse_nil/request.json +++ b/test/request-response-samples/Dummy__should_parse_nil/request.json @@ -1,6 +1,6 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": null + "DummyStringFilter": null } } diff --git a/test/request-response-samples/Dummy__should_parse_nil/request.xml b/test/request-response-samples/Dummy__should_parse_nil/request.xml index 94e9e418b..aabb3bf04 100644 --- a/test/request-response-samples/Dummy__should_parse_nil/request.xml +++ b/test/request-response-samples/Dummy__should_parse_nil/request.xml @@ -1 +1 @@ -Humpty \ No newline at end of file +Humpty \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_use_defined_value_key/request.json b/test/request-response-samples/Dummy__should_use_defined_value_key/request.json index 5d29a0b5d..1a89d6b8a 100644 --- a/test/request-response-samples/Dummy__should_use_defined_value_key/request.json +++ b/test/request-response-samples/Dummy__should_use_defined_value_key/request.json @@ -1,11 +1,11 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" }, "DummyAccount": { "DummyName": "Dummy", "DummyPassword": "1234" } -} \ No newline at end of file +} diff --git a/test/request-response-samples/Dummy__should_use_defined_value_key/request.xml b/test/request-response-samples/Dummy__should_use_defined_value_key/request.xml index b9c5a2b8b..2bb3fb2e8 100644 --- a/test/request-response-samples/Dummy__should_use_defined_value_key/request.xml +++ b/test/request-response-samples/Dummy__should_use_defined_value_key/request.xml @@ -1 +1 @@ -HumptyDumptyDummy1234 \ No newline at end of file +HumptyDumptyDummy1234 \ No newline at end of file diff --git a/test/request-response-samples/Dummy__should_work_with_imported_schemas/request.json b/test/request-response-samples/Dummy__should_work_with_imported_schemas/request.json index 1f3a2941d..52d6f57f7 100644 --- a/test/request-response-samples/Dummy__should_work_with_imported_schemas/request.json +++ b/test/request-response-samples/Dummy__should_work_with_imported_schemas/request.json @@ -1,6 +1,6 @@ { "DummyField1": "Humpty", "DummyFilter": { - "DummyFilterString": "Dumpty" + "DummyStringFilter": "Dumpty" } -} \ No newline at end of file +} diff --git a/test/request-response-samples/Dummy__should_work_with_imported_schemas/request.xml b/test/request-response-samples/Dummy__should_work_with_imported_schemas/request.xml index c28cceff9..e377aaa35 100644 --- a/test/request-response-samples/Dummy__should_work_with_imported_schemas/request.xml +++ b/test/request-response-samples/Dummy__should_work_with_imported_schemas/request.xml @@ -1 +1 @@ -HumptyDumpty \ No newline at end of file +HumptyDumpty \ No newline at end of file diff --git a/test/request-response-samples/Message__Messages_suffixed_with_In_Out/response.json b/test/request-response-samples/Message__Messages_suffixed_with_In_Out/response.json index d3aacc842..79fdba308 100644 --- a/test/request-response-samples/Message__Messages_suffixed_with_In_Out/response.json +++ b/test/request-response-samples/Message__Messages_suffixed_with_In_Out/response.json @@ -1,4 +1,4 @@ { "return":"-1", - "bstrError":{} + "bstrError":"" } diff --git a/test/request-response-samples/Message__Messages_suffixed_with_Input_Output/response.json b/test/request-response-samples/Message__Messages_suffixed_with_Input_Output/response.json index d3aacc842..79fdba308 100644 --- a/test/request-response-samples/Message__Messages_suffixed_with_Input_Output/response.json +++ b/test/request-response-samples/Message__Messages_suffixed_with_Input_Output/response.json @@ -1,4 +1,4 @@ { "return":"-1", - "bstrError":{} + "bstrError":"" } diff --git a/test/request-response-samples/Message__Messages_suffixed_with_Request_Response/response.json b/test/request-response-samples/Message__Messages_suffixed_with_Request_Response/response.json index d3aacc842..79fdba308 100644 --- a/test/request-response-samples/Message__Messages_suffixed_with_Request_Response/response.json +++ b/test/request-response-samples/Message__Messages_suffixed_with_Request_Response/response.json @@ -1,4 +1,4 @@ { "return":"-1", - "bstrError":{} + "bstrError":"" } diff --git a/test/request-response-samples/Message__overriding_namespace_prefix/request.json b/test/request-response-samples/Message__overriding_namespace_prefix/request.json new file mode 100644 index 000000000..138f385fd --- /dev/null +++ b/test/request-response-samples/Message__overriding_namespace_prefix/request.json @@ -0,0 +1,3 @@ +{ + "s1:bstrResourceId": "034b7ea5-8a04-11e3-9710-0050569575d8" +} diff --git a/test/request-response-samples/Message__overriding_namespace_prefix/request.xml b/test/request-response-samples/Message__overriding_namespace_prefix/request.xml new file mode 100644 index 000000000..e11e9cee0 --- /dev/null +++ b/test/request-response-samples/Message__overriding_namespace_prefix/request.xml @@ -0,0 +1 @@ +034b7ea5-8a04-11e3-9710-0050569575d8 \ No newline at end of file diff --git a/test/request-response-samples/Message__overriding_namespace_prefix/response.json b/test/request-response-samples/Message__overriding_namespace_prefix/response.json new file mode 100644 index 000000000..79fdba308 --- /dev/null +++ b/test/request-response-samples/Message__overriding_namespace_prefix/response.json @@ -0,0 +1,4 @@ +{ + "return":"-1", + "bstrError":"" +} diff --git a/test/request-response-samples/Message__overriding_namespace_prefix/response.xml b/test/request-response-samples/Message__overriding_namespace_prefix/response.xml new file mode 100644 index 000000000..17169f0b0 --- /dev/null +++ b/test/request-response-samples/Message__overriding_namespace_prefix/response.xml @@ -0,0 +1 @@ +-1 \ No newline at end of file diff --git a/test/request-response-samples/Message__overriding_namespace_prefix/soap.wsdl b/test/request-response-samples/Message__overriding_namespace_prefix/soap.wsdl new file mode 100644 index 000000000..85deeaa7e --- /dev/null +++ b/test/request-response-samples/Message__overriding_namespace_prefix/soap.wsdl @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/request.json b/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/request.json new file mode 100644 index 000000000..19765bd50 --- /dev/null +++ b/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/request.json @@ -0,0 +1 @@ +null diff --git a/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/request.xml b/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/request.xml new file mode 100644 index 000000000..b198acbc3 --- /dev/null +++ b/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/request.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/soap.wsdl b/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/soap.wsdl new file mode 100644 index 000000000..99cf04677 --- /dev/null +++ b/test/request-response-samples/NoParams__add_correct_namespace_for_operations_without_parameters/soap.wsdl @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/request-response-samples/README.md b/test/request-response-samples/README.md index 429125b95..74f03ce67 100644 --- a/test/request-response-samples/README.md +++ b/test/request-response-samples/README.md @@ -18,3 +18,4 @@ Follow this process to add samples: * `response.json` - This is the expected JSON from parsing the response XML * `error_response.json` - This is the expected JSON root attached to the error object when a fault occurs * `soap.wsdl` - This is the WSDL that defines the operation and messages + * `wsdl_options.wsdl` - This is the wsdl options to create request. (ignorenamespacers, ignoredbasednamespaces). diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/request.json b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/request.json new file mode 100644 index 000000000..6f31cf5a2 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/request.json @@ -0,0 +1 @@ +{ } \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/request.xml b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/request.xml new file mode 100644 index 000000000..a31a27bb6 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/request.xml @@ -0,0 +1 @@ +2014-10-12T01:02:03Z2014-10-12T01:12:03Zbasicuserbasicpass2014-10-12T01:02:03Z \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/response.json b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/response.json new file mode 100644 index 000000000..6f31cf5a2 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/response.json @@ -0,0 +1 @@ +{ } \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/response.xml b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/response.xml new file mode 100644 index 000000000..43591321a --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/response.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/security.json b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/security.json new file mode 100644 index 000000000..48ca3a285 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/security.json @@ -0,0 +1,6 @@ +{ + "type": "ws", + "username": "basicuser", + "password": "basicpass", + "options": "PasswordText" +} diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/soap.wsdl b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/soap.wsdl new file mode 100644 index 000000000..809710161 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security backward compatible check/soap.wsdl @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security/request.xml b/test/request-response-samples/RequestHeaders__propogate_basic_security/request.xml index 8a802f488..a31a27bb6 100644 --- a/test/request-response-samples/RequestHeaders__propogate_basic_security/request.xml +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security/request.xml @@ -1 +1 @@ -2014-10-12T01:02:03Z2014-10-12T01:12:03Zbasicuserbasicpass2014-10-12T01:02:03Z \ No newline at end of file +2014-10-12T01:02:03Z2014-10-12T01:12:03Zbasicuserbasicpass2014-10-12T01:02:03Z \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/request.json b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/request.json new file mode 100644 index 000000000..6f31cf5a2 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/request.json @@ -0,0 +1 @@ +{ } \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/request.xml b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/request.xml new file mode 100644 index 000000000..a31a27bb6 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/request.xml @@ -0,0 +1 @@ +2014-10-12T01:02:03Z2014-10-12T01:12:03Zbasicuserbasicpass2014-10-12T01:02:03Z \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/response.json b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/response.json new file mode 100644 index 000000000..6f31cf5a2 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/response.json @@ -0,0 +1 @@ +{ } \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/response.xml b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/response.xml new file mode 100644 index 000000000..43591321a --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/response.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/security.json b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/security.json new file mode 100644 index 000000000..e4cd00427 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/security.json @@ -0,0 +1,9 @@ +{ + "type": "ws", + "username": "basicuser", + "password": "basicpass", + "options": { + "passwordType": "invalid", + "hasTimeStamp": "" + } +} diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/soap.wsdl b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/soap.wsdl new file mode 100644 index 000000000..809710161 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_invalid_options/soap.wsdl @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/request.json b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/request.json new file mode 100644 index 000000000..6f31cf5a2 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/request.json @@ -0,0 +1 @@ +{ } \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/request.xml b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/request.xml new file mode 100644 index 000000000..dce627ca4 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/request.xml @@ -0,0 +1 @@ +basicuserbasicpass2014-10-12T01:02:03Z \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/response.json b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/response.json new file mode 100644 index 000000000..6f31cf5a2 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/response.json @@ -0,0 +1 @@ +{ } \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/response.xml b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/response.xml new file mode 100644 index 000000000..43591321a --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/response.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/security.json b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/security.json new file mode 100644 index 000000000..2d7e2cc40 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/security.json @@ -0,0 +1,8 @@ +{ + "type": "ws", + "username": "basicuser", + "password": "basicpass", + "options": { + "hasTimeStamp": false + } +} diff --git a/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/soap.wsdl b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/soap.wsdl new file mode 100644 index 000000000..809710161 --- /dev/null +++ b/test/request-response-samples/RequestHeaders__propogate_basic_security_no_timestamp/soap.wsdl @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/Common.xsd b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/Common.xsd new file mode 100644 index 000000000..7c5b0cc6f --- /dev/null +++ b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/Common.xsd @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/Name.xsd b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/Name.xsd new file mode 100644 index 000000000..aad085bd0 --- /dev/null +++ b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/Name.xsd @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + Collection of NamePhone + + + + + + + + + + + + + + + + + Collection of NameAddress + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/request.json b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/request.json new file mode 100644 index 000000000..4e041341d --- /dev/null +++ b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/request.json @@ -0,0 +1,44 @@ +{ + "Profile": { + "IDs": { + "UniqueID": [ + { + "attributes": { + "source": "TESTSOURCE" + }, + "$value": 100 + } + ] + }, + "Addresses": { + "NameAddress": [ + { + "AddressLine": "Another Address" + }, + { + "AddressLine": "My Address" + } + ] + }, + "Phones": { + "NamePhone": [ + { + "attributes": { + "primary": true + }, + "PhoneData": { + "PhoneNumber": "123" + } + }, + { + "attributes": { + "primary": false + }, + "PhoneData": { + "PhoneNumber": "456" + } + } + ] + } + } +} diff --git a/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/request.xml b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/request.xml new file mode 100644 index 000000000..8ed251f36 --- /dev/null +++ b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/request.xml @@ -0,0 +1 @@ +100Another AddressMy Address123456 \ No newline at end of file diff --git a/test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/response.json b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/response.json similarity index 100% rename from test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/response.json rename to test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/response.json diff --git a/test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/response.xml b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/response.xml similarity index 100% rename from test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/response.xml rename to test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/response.xml diff --git a/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/soap.wsdl b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/soap.wsdl new file mode 100644 index 000000000..2076cd354 --- /dev/null +++ b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/soap.wsdl @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/wsdl_options.json b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/wsdl_options.json new file mode 100644 index 000000000..4252c1564 --- /dev/null +++ b/test/request-response-samples/UpdateProfile__correct_namespaces_for_elements_with_base_ignorednamespaces/wsdl_options.json @@ -0,0 +1,3 @@ +{ + "ignoreBaseNameSpaces": true +} diff --git a/test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/Common.xsd b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/Common.xsd similarity index 100% rename from test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/Common.xsd rename to test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/Common.xsd diff --git a/test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/Name.xsd b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/Name.xsd similarity index 100% rename from test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/Name.xsd rename to test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/Name.xsd diff --git a/test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/request.json b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/request.json similarity index 100% rename from test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/request.json rename to test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/request.json diff --git a/test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/request.xml b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/request.xml similarity index 100% rename from test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/request.xml rename to test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/request.xml diff --git a/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/response.json b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/response.json new file mode 100644 index 000000000..8e70dca20 --- /dev/null +++ b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/response.json @@ -0,0 +1,17 @@ +{ + "Result":{ + "attributes": { + "resultStatusFlag": "SUCCESS" + }, + "IDs": { + "UniqueID": [ + { + "attributes": { + "source": "TESTSOURCE" + }, + "$value": "100" + } + ] + } + } +} \ No newline at end of file diff --git a/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/response.xml b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/response.xml new file mode 100644 index 000000000..223079d90 --- /dev/null +++ b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/response.xml @@ -0,0 +1 @@ +100 \ No newline at end of file diff --git a/test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/soap.wsdl b/test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/soap.wsdl similarity index 100% rename from test/request-response-samples/UpdateProfile__correct_nanespaces_in_sequence_with_imports/soap.wsdl rename to test/request-response-samples/UpdateProfile__correct_namespaces_in_sequence_with_imports/soap.wsdl diff --git a/test/request-response-samples/addList__complex_extension_namespace_for_arrays/request.xml b/test/request-response-samples/addList__complex_extension_namespace_for_arrays/request.xml index 4b4157ac4..32f13950d 100644 --- a/test/request-response-samples/addList__complex_extension_namespace_for_arrays/request.xml +++ b/test/request-response-samples/addList__complex_extension_namespace_for_arrays/request.xml @@ -1 +1 @@ -First-1400867779067Sullivan800-555-2819aLFirst-1400867779067Sullivan800-555-2819 \ No newline at end of file +First-1400867779067Sullivan800-555-2819aLFirst-1400867779067Sullivan800-555-2819 \ No newline at end of file diff --git a/test/request-response-samples/multiCall__rpc_stype_request_xsi_attributes/request.xml b/test/request-response-samples/multiCall__rpc_stype_request_xsi_attributes/request.xml index 60592e7d9..1925d93a2 100644 --- a/test/request-response-samples/multiCall__rpc_stype_request_xsi_attributes/request.xml +++ b/test/request-response-samples/multiCall__rpc_stype_request_xsi_attributes/request.xml @@ -1 +1 @@ -sessionIdproduct_stock.updateHTC Touch Diamondqty9199is_in_stock1 \ No newline at end of file +sessionIdproduct_stock.updateHTC Touch Diamondqty9199is_in_stock1 \ No newline at end of file diff --git a/test/request-response-samples/readMetadata__should_respect_xsi_type_attribute/request.json b/test/request-response-samples/readMetadata__should_respect_xsi_type_attribute/request.json index be09765bc..4038e679d 100644 --- a/test/request-response-samples/readMetadata__should_respect_xsi_type_attribute/request.json +++ b/test/request-response-samples/readMetadata__should_respect_xsi_type_attribute/request.json @@ -1,4 +1,4 @@ { - "metadataType": "CustomObject", + "type": "CustomObject", "fullNames": "Opportunity" } diff --git a/test/request-response-samples/readMetadata__should_respect_xsi_type_attribute/request.xml b/test/request-response-samples/readMetadata__should_respect_xsi_type_attribute/request.xml index be2906b27..1957d562b 100644 --- a/test/request-response-samples/readMetadata__should_respect_xsi_type_attribute/request.xml +++ b/test/request-response-samples/readMetadata__should_respect_xsi_type_attribute/request.xml @@ -1 +1 @@ -CustomObjectOpportunity \ No newline at end of file +CustomObjectOpportunity \ No newline at end of file diff --git a/test/request-response-samples/update__complex_extension_with_array_attributes/request.xml b/test/request-response-samples/update__complex_extension_with_array_attributes/request.xml index beff89e9d..2eccb794b 100644 --- a/test/request-response-samples/update__complex_extension_with_array_attributes/request.xml +++ b/test/request-response-samples/update__complex_extension_with_array_attributes/request.xml @@ -1 +1 @@ -AllisterSullivanofficephonemobilephone800-555-2819falsetrueBilling CommunicationtrueMarketing \ No newline at end of file +AllisterSullivanofficephonemobilephone800-555-2819falsetrueBilling CommunicationtrueMarketing \ No newline at end of file diff --git a/test/security/ClientSSLSecurityPFX.js b/test/security/ClientSSLSecurityPFX.js new file mode 100644 index 000000000..d98694222 --- /dev/null +++ b/test/security/ClientSSLSecurityPFX.js @@ -0,0 +1,100 @@ +'use strict'; + +var fs = require('fs'), + join = require('path').join; + +describe('ClientSSLSecurityPFX', function() { + var ClientSSLSecurityPFX = require('../../').ClientSSLSecurityPFX; + var pfx = __filename; + + it('should be function', function() { + ClientSSLSecurityPFX.should.be.type('function'); + }); + + describe('defaultOption param', function() { + it('should be accepted as the second param', function() { + new ClientSSLSecurityPFX(null, {}); + }); + + it('should be used in addOptions', function() { + var options = {}; + var defaultOptions = { foo: 5 }; + var instance = new ClientSSLSecurityPFX(null, defaultOptions); + instance.addOptions(options); + options.should.have.property("foo", 5); + }); + }); + + it('should throw if invalid pfk file is given', function () { + var instanceCert = null; + + try { + instanceCert = new ClientSSLSecurityPFX({}); + } catch (e) { + //should happen! + instanceCert = false; + } + + if (instanceCert !== false) { + throw new Error('accepted wrong pfk'); + } + }); + + it('should be usable in a request', function (done) { + var https = require('https'); + var pfkBuffer = fs.readFileSync(join(__dirname, '..', 'certs', 'client-password.pfx')), + instance; + + instance = new ClientSSLSecurityPFX(pfkBuffer, 'test2test'); + var soptions = { + host: 'localhost', + port: 1338, + requestCert: true, + rejectUnauthorized: false, + pfx: fs.readFileSync(join(__dirname, '..', 'certs', 'server-password.pfx')), + passphrase: 'test2test', + }; + var options = { + port: 1338 + }; + instance.addOptions(options); + + var server = https.createServer(soptions, function(req, res) { + req.socket.should.have.property('authorized', true); + // Doesn't work in older versions of nodejs + // req.socket.should.have.property('authorizationError', null); + res.writeHead(200); + res.end('OK'); + }); + + server.listen(soptions.port, soptions.host, function() { + var data = ''; + + https.get(options, function(res) { + res.on('data', function(data_) { data += data_; }); + res.on('end', function() { + server.close(); + data.should.equal('OK'); + done(); + }); + }); + }); + }); + + it('should accept a passphrase as argument for the pfx cert', function () { + var pfkBuffer = fs.readFileSync(join(__dirname, '..', 'certs', 'client-password.pfx')), + instance; + + instance = new ClientSSLSecurityPFX(pfkBuffer, 'test2est'); + instance.should.have.property("pfx", pfkBuffer); + instance.should.have.property("passphrase", 'test2est'); + }); + + it('should accept a Buffer as argument for the pfx cert', function () { + var pfkBuffer = fs.readFileSync(join(__dirname, '..', 'certs', 'pfk-buffer.pfx')), + instance; + + instance = new ClientSSLSecurityPFX(pfkBuffer); + instance.should.have.property("pfx", pfkBuffer); + }); +}); diff --git a/test/server-test.js b/test/server-test.js index 8a6522e14..b3b37b91b 100644 --- a/test/server-test.js +++ b/test/server-test.js @@ -4,7 +4,8 @@ var fs = require('fs'), soap = require('..'), assert = require('assert'), request = require('request'), - http = require('http'); + http = require('http'), + lastReqAddress; var test = {}; test.server = null; @@ -18,7 +19,7 @@ test.service = { throw new Error('triggered server error'); } else if (args.tickerSymbol === 'Async') { return cb({ price: 19.56 }); - } else if (args.tickerSymbol === 'SOAP Fault') { + } else if (args.tickerSymbol === 'SOAP Fault v1.2') { throw { Fault: { Code: { @@ -28,6 +29,13 @@ test.service = { Reason: { Text: "Processing Error" } } }; + } else if (args.tickerSymbol === 'SOAP Fault v1.1') { + throw { + Fault: { + faultcode: "soap:Client.BadArguments", + faultstring: "Error while processing arguments" + } + }; } else { return { price: 19.56 }; } @@ -36,21 +44,25 @@ test.service = { SetTradePrice: function(args, cb, soapHeader) { }, - IsValidPrice: function(args, cb, soapHeader) { + IsValidPrice: function(args, cb, soapHeader, req) { + lastReqAddress = req.connection.remoteAddress; + var validationError = { Fault: { Code: { Value: "soap:Sender", Subcode: { value: "rpc:BadArguments" } }, - Reason: { Text: "Processing Error" } + Reason: { Text: "Processing Error" }, + statusCode: 500 } }; var isValidPrice = function() { var price = args.price; - if(isNaN(price) || (price === ' ')) + if(isNaN(price) || (price === ' ')) { return cb(validationError); + } price = parseInt(price, 10); var validPrice = (price > 0 && price < Math.pow(10, 5)); @@ -119,6 +131,27 @@ describe('SOAP Server', function() { }); }); + it('should 500 on wrong message', function(done) { + request.post({ + url: test.baseUrl + '/stockquote?wsdl', + body : '' + + ' ' + + ' ' + + ' ' + + ' ' + + '', + headers: {'Content-Type': 'text/xml'} + }, function(err, res, body) { + assert.ok(!err); + assert.equal(res.statusCode, 500); + assert.ok(body.length); + done(); + } + ); + }); + it('should server up WSDL', function(done) { request(test.baseUrl + '/stockquote?wsdl', function(err, res, body) { assert.ok(!err); @@ -172,12 +205,25 @@ describe('SOAP Server', function() { }); }); + it('should pass the original req to async methods', function(done) { + soap.createClient(test.baseUrl + '/stockquote?wsdl', function(err, client) { + assert.ok(!err); + client.IsValidPrice({ price: 50000 }, function(err, result) { + // node V3.x+ reports addresses as IPV6 + var addressParts = lastReqAddress.split(':'); + addressParts[(addressParts.length - 1)].should.equal('127.0.0.1'); + done(); + }); + }); + }); + it('should return correct async errors', function(done) { soap.createClient(test.baseUrl + '/stockquote?wsdl', function(err, client) { assert.ok(!err); client.IsValidPrice({ price: "invalid_price"}, function(err, result) { assert.ok(err); assert.ok(err.root.Envelope.Body.Fault); + assert.equal(err.response.statusCode, 500); done(); }); }); @@ -259,14 +305,40 @@ describe('SOAP Server', function() { }); }); - it('should return SOAP Fault body', function(done) { + it('should return SOAP Fault body for SOAP 1.2', function(done) { soap.createClient(test.baseUrl + '/stockquote?wsdl', function(err, client) { assert.ok(!err); - client.GetLastTradePrice({ tickerSymbol: 'SOAP Fault' }, function(err, response, body) { + client.GetLastTradePrice({ tickerSymbol: 'SOAP Fault v1.2' }, function(err, response, body) { assert.ok(err); var fault = err.root.Envelope.Body.Fault; + assert.equal(err.message, fault.faultcode + ': ' + fault.faultstring); assert.equal(fault.Code.Value, "soap:Sender"); assert.equal(fault.Reason.Text, "Processing Error"); + // Verify namespace on elements set according to fault spec 1.2 + assert.ok(body.match(/.*<\/soap:Code>/g), + "Body should contain Code-element with namespace"); + assert.ok(body.match(/.*<\/soap:Reason>/g), + "Body should contain Reason-element with namespace"); + assert.equal(err.response.statusCode, 200); + done(); + }); + }); + }); + + it('should return SOAP Fault body for SOAP 1.1', function(done) { + soap.createClient(test.baseUrl + '/stockquote?wsdl', function(err, client) { + assert.ok(!err); + client.GetLastTradePrice({ tickerSymbol: 'SOAP Fault v1.1' }, function(err, response, body) { + assert.ok(err); + var fault = err.root.Envelope.Body.Fault; + assert.equal(err.message, fault.faultcode + ': ' + fault.faultstring); + assert.equal(fault.faultcode, "soap:Client.BadArguments"); + assert.equal(fault.faultstring, "Error while processing arguments"); + // Verify namespace on elements set according to fault spec 1.1 + assert.ok(body.match(/.*<\/faultcode>/g), + "Body should contain faultcode-element without namespace"); + assert.ok(body.match(/.*<\/faultstring>/g), + "Body should contain faultstring-element without namespace"); done(); }); }); diff --git a/test/wsdl-parse-test.js b/test/wsdl-parse-test.js index 1178a0079..eeb90080d 100644 --- a/test/wsdl-parse-test.js +++ b/test/wsdl-parse-test.js @@ -14,4 +14,11 @@ describe(__filename, function () { done(); }); }); + + it('should parse recursive wsdls', function (done) { + open_wsdl(path.resolve(__dirname, 'wsdl/recursive/file.wsdl'), function (err, def) { + // If we get here then we succeeded + done( err ); + }); + }); }); diff --git a/test/wsdl-test.js b/test/wsdl-test.js index a01c0f662..693c6df3b 100644 --- a/test/wsdl-test.js +++ b/test/wsdl-test.js @@ -73,6 +73,34 @@ wsdlStrictTests['should get the parent namespace when parent namespace is empty }); }; +wsdlStrictTests['should handle element ref'] = function(done) { + var expectedMsg = '' + + '' + + '001' + + ''; + soap.createClient(__dirname + '/wsdl/elementref/foo.wsdl', {strict: true}, function(err, client) { + assert.ok(!err); + client.fooOp({paymentRq: {bankSvcRq: {requestUID: '001'}}}, function(err, result) { + assert.equal(client.lastMessage, expectedMsg); + done(); + }); + }); +}; + +wsdlStrictTests['should handle type ref'] = function(done) { + var expectedMsg = require('./wsdl/typeref/request.xml.js'); + var reqJson = require('./wsdl/typeref/request.json'); + soap.createClient(__dirname + '/wsdl/typeref/order.wsdl', {strict: true}, function(err, client) { + assert.ok(!err); + client.order(reqJson, function(err, result) { + assert.equal(client.lastMessage, expectedMsg); + done(); + }); + }); +}; + module.exports = { 'WSDL Parser (strict)': wsdlStrictTests, 'WSDL Parser (non-strict)': wsdlNonStrictTests diff --git a/test/wsdl/default_namespace_soap12.wsdl b/test/wsdl/default_namespace_soap12.wsdl new file mode 100644 index 000000000..8e57b6fc1 --- /dev/null +++ b/test/wsdl/default_namespace_soap12.wsdl @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/wsdl/elementref/bar.xsd b/test/wsdl/elementref/bar.xsd new file mode 100644 index 000000000..f87405d4f --- /dev/null +++ b/test/wsdl/elementref/bar.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test/wsdl/elementref/bar1.xsd b/test/wsdl/elementref/bar1.xsd new file mode 100644 index 000000000..a8432d487 --- /dev/null +++ b/test/wsdl/elementref/bar1.xsd @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/wsdl/elementref/foo.wsdl b/test/wsdl/elementref/foo.wsdl new file mode 100755 index 000000000..0f81b1913 --- /dev/null +++ b/test/wsdl/elementref/foo.wsdl @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/wsdl/recursive/A.xsd b/test/wsdl/recursive/A.xsd new file mode 100644 index 000000000..fac598a6e --- /dev/null +++ b/test/wsdl/recursive/A.xsd @@ -0,0 +1,25 @@ + + + + + + Defines the method of contact to reach a party (in + their specified role) + + + + + + Indicates that this contact medium is the + preferred (if multiple are present). Lower integer takes + precedence, "equals are equals" so the value does not need to be + unique. + + + + + + + \ No newline at end of file diff --git a/test/wsdl/recursive/B.xsd b/test/wsdl/recursive/B.xsd new file mode 100644 index 000000000..97ef0a23c --- /dev/null +++ b/test/wsdl/recursive/B.xsd @@ -0,0 +1,25 @@ + + + + + + + Defines the method of contact to reach a party (in + their specified role) + + + + + + Indicates that this contact medium is the + preferred (if multiple are present). Lower integer takes + precedence, "equals are equals" so the value does not need to be + unique. + + + + + + + \ No newline at end of file diff --git a/test/wsdl/recursive/file.wsdl b/test/wsdl/recursive/file.wsdl new file mode 100644 index 000000000..554f6d81a --- /dev/null +++ b/test/wsdl/recursive/file.wsdl @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/wsdl/typeref/ns1.xsd b/test/wsdl/typeref/ns1.xsd new file mode 100644 index 000000000..243ec1947 --- /dev/null +++ b/test/wsdl/typeref/ns1.xsd @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/wsdl/typeref/ns2.xsd b/test/wsdl/typeref/ns2.xsd new file mode 100644 index 000000000..503fe1412 --- /dev/null +++ b/test/wsdl/typeref/ns2.xsd @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/wsdl/typeref/order.wsdl b/test/wsdl/typeref/order.wsdl new file mode 100755 index 000000000..210bafe00 --- /dev/null +++ b/test/wsdl/typeref/order.wsdl @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/wsdl/typeref/request.json b/test/wsdl/typeref/request.json new file mode 100644 index 000000000..7177d08d6 --- /dev/null +++ b/test/wsdl/typeref/request.json @@ -0,0 +1,15 @@ +{ + "itemRq": { + "ecomRq": { + "rqUID": "001" + }, + "item": { + "qty": 100, + "itemId": "item01" + }, + "backupItem": { + "qty": 50, + "itemId": "item02" + } + } +} diff --git a/test/wsdl/typeref/request.xml.js b/test/wsdl/typeref/request.xml.js new file mode 100755 index 000000000..8d19de678 --- /dev/null +++ b/test/wsdl/typeref/request.xml.js @@ -0,0 +1,6 @@ +module.exports = '' + + '001' + + '100' + + 'item01' + + '50' + + 'item02';