571 lines
23 KiB
JavaScript
571 lines
23 KiB
JavaScript
"use strict";
|
|
/*
|
|
* Copyright (c) 2011 Vinay Pulim <vinay@milewise.com>
|
|
* MIT Licensed
|
|
*/
|
|
var __extends = (this && this.__extends) || (function () {
|
|
var extendStatics = function (d, b) {
|
|
extendStatics = Object.setPrototypeOf ||
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
return extendStatics(d, b);
|
|
};
|
|
return function (d, b) {
|
|
extendStatics(d, b);
|
|
function __() { this.constructor = d; }
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
};
|
|
})();
|
|
exports.__esModule = true;
|
|
exports.Server = void 0;
|
|
var events_1 = require("events");
|
|
var url = require("url");
|
|
var utils_1 = require("./utils");
|
|
var zlib;
|
|
try {
|
|
zlib = require('zlib');
|
|
}
|
|
catch (error) {
|
|
}
|
|
function isExpress(server) {
|
|
return (typeof server.route === 'function' && typeof server.use === 'function');
|
|
}
|
|
function isPromiseLike(obj) {
|
|
return (!!obj && typeof obj.then === 'function');
|
|
}
|
|
function getDateString(d) {
|
|
function pad(n) {
|
|
return n < 10 ? '0' + n : n;
|
|
}
|
|
return d.getUTCFullYear() + '-'
|
|
+ pad(d.getUTCMonth() + 1) + '-'
|
|
+ pad(d.getUTCDate()) + 'T'
|
|
+ pad(d.getUTCHours()) + ':'
|
|
+ pad(d.getUTCMinutes()) + ':'
|
|
+ pad(d.getUTCSeconds()) + 'Z';
|
|
}
|
|
var Server = /** @class */ (function (_super) {
|
|
__extends(Server, _super);
|
|
function Server(server, path, services, wsdl, options) {
|
|
var _this_1 = _super.call(this) || this;
|
|
options = options || {
|
|
path: path,
|
|
services: services
|
|
};
|
|
_this_1.path = path;
|
|
_this_1.services = services;
|
|
_this_1.wsdl = wsdl;
|
|
_this_1.suppressStack = options && options.suppressStack;
|
|
_this_1.returnFault = options && options.returnFault;
|
|
_this_1.onewayOptions = options && options.oneWay || {};
|
|
_this_1.enableChunkedEncoding =
|
|
options.enableChunkedEncoding === undefined ? true : !!options.enableChunkedEncoding;
|
|
_this_1.callback = options.callback ? options.callback : function () { };
|
|
if (path[path.length - 1] !== '/') {
|
|
path += '/';
|
|
}
|
|
wsdl.onReady(function (err) {
|
|
if (isExpress(server)) {
|
|
// handle only the required URL path for express server
|
|
server.route(path).all(function (req, res) {
|
|
if (typeof _this_1.authorizeConnection === 'function') {
|
|
if (!_this_1.authorizeConnection(req, res)) {
|
|
res.end();
|
|
return;
|
|
}
|
|
}
|
|
_this_1._requestListener(req, res);
|
|
});
|
|
_this_1.callback(err, _this_1);
|
|
}
|
|
else {
|
|
var listeners_1 = server.listeners('request').slice();
|
|
server.removeAllListeners('request');
|
|
server.addListener('request', function (req, res) {
|
|
if (typeof _this_1.authorizeConnection === 'function') {
|
|
if (!_this_1.authorizeConnection(req, res)) {
|
|
res.end();
|
|
return;
|
|
}
|
|
}
|
|
var reqPath = url.parse(req.url).pathname;
|
|
if (reqPath[reqPath.length - 1] !== '/') {
|
|
reqPath += '/';
|
|
}
|
|
if (path === reqPath) {
|
|
_this_1._requestListener(req, res);
|
|
}
|
|
else {
|
|
for (var i = 0, len = listeners_1.length; i < len; i++) {
|
|
listeners_1[i].call(_this_1, req, res);
|
|
}
|
|
}
|
|
});
|
|
_this_1.callback(err, _this_1);
|
|
}
|
|
});
|
|
_this_1._initializeOptions(options);
|
|
return _this_1;
|
|
}
|
|
Server.prototype.addSoapHeader = function (soapHeader, name, namespace, xmlns) {
|
|
if (!this.soapHeaders) {
|
|
this.soapHeaders = [];
|
|
}
|
|
soapHeader = this._processSoapHeader(soapHeader, name, namespace, xmlns);
|
|
return this.soapHeaders.push(soapHeader) - 1;
|
|
};
|
|
Server.prototype.changeSoapHeader = function (index, soapHeader, name, namespace, xmlns) {
|
|
if (!this.soapHeaders) {
|
|
this.soapHeaders = [];
|
|
}
|
|
soapHeader = this._processSoapHeader(soapHeader, name, namespace, xmlns);
|
|
this.soapHeaders[index] = soapHeader;
|
|
};
|
|
Server.prototype.getSoapHeaders = function () {
|
|
return this.soapHeaders;
|
|
};
|
|
Server.prototype.clearSoapHeaders = function () {
|
|
this.soapHeaders = null;
|
|
};
|
|
Server.prototype._processSoapHeader = function (soapHeader, name, namespace, xmlns) {
|
|
switch (typeof soapHeader) {
|
|
case 'object':
|
|
return this.wsdl.objectToXML(soapHeader, name, namespace, xmlns, true);
|
|
case 'function':
|
|
var _this_2 = this;
|
|
// arrow function does not support arguments variable
|
|
// tslint:disable-next-line
|
|
return function () {
|
|
var result = soapHeader.apply(null, arguments);
|
|
if (typeof result === 'object') {
|
|
return _this_2.wsdl.objectToXML(result, name, namespace, xmlns, true);
|
|
}
|
|
else {
|
|
return result;
|
|
}
|
|
};
|
|
default:
|
|
return soapHeader;
|
|
}
|
|
};
|
|
Server.prototype._initializeOptions = function (options) {
|
|
this.wsdl.options.attributesKey = options.attributesKey || 'attributes';
|
|
this.onewayOptions.statusCode = this.onewayOptions.responseCode || 200;
|
|
this.onewayOptions.emptyBody = !!this.onewayOptions.emptyBody;
|
|
};
|
|
Server.prototype._processRequestXml = function (req, res, xml) {
|
|
var _this_1 = this;
|
|
var error;
|
|
try {
|
|
if (typeof this.log === 'function') {
|
|
this.log('received', xml);
|
|
}
|
|
this._process(xml, req, res, function (result, statusCode) {
|
|
_this_1._sendHttpResponse(res, statusCode, result);
|
|
if (typeof _this_1.log === 'function') {
|
|
_this_1.log('replied', result);
|
|
}
|
|
});
|
|
}
|
|
catch (err) {
|
|
if (err.Fault !== undefined) {
|
|
return this._sendError(err.Fault, function (result, statusCode) {
|
|
_this_1._sendHttpResponse(res, statusCode || 500, result);
|
|
if (typeof _this_1.log === 'function') {
|
|
_this_1.log('error', err);
|
|
}
|
|
}, new Date().toISOString());
|
|
}
|
|
else {
|
|
error = err.stack ? (this.suppressStack === true ? err.message : err.stack) : err;
|
|
this._sendHttpResponse(res, /* statusCode */ 500, error);
|
|
if (typeof this.log === 'function') {
|
|
this.log('error', error);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
Server.prototype._requestListener = function (req, res) {
|
|
var _this_1 = this;
|
|
var reqParse = url.parse(req.url);
|
|
var reqPath = reqParse.pathname;
|
|
var reqQuery = reqParse.search;
|
|
if (typeof this.log === 'function') {
|
|
this.log('info', 'Handling ' + req.method + ' on ' + req.url);
|
|
}
|
|
if (req.method === 'GET') {
|
|
if (reqQuery && reqQuery.toLowerCase().startsWith('?wsdl')) {
|
|
if (typeof this.log === 'function') {
|
|
this.log('info', 'Wants the WSDL');
|
|
}
|
|
res.setHeader('Content-Type', 'application/xml');
|
|
res.write(this.wsdl.toXML());
|
|
}
|
|
res.end();
|
|
}
|
|
else if (req.method === 'POST') {
|
|
if (typeof req.headers['content-type'] !== 'undefined') {
|
|
res.setHeader('Content-Type', req.headers['content-type']);
|
|
}
|
|
else {
|
|
res.setHeader('Content-Type', 'application/xml');
|
|
}
|
|
// request body is already provided by an express middleware
|
|
// in this case unzipping should also be done by the express middleware itself
|
|
if (req.body && req.body.length > 0) {
|
|
return this._processRequestXml(req, res, req.body.toString());
|
|
}
|
|
var chunks_1 = [];
|
|
var gunzip = void 0;
|
|
var source = req;
|
|
if (req.headers['content-encoding'] === 'gzip') {
|
|
gunzip = zlib.createGunzip();
|
|
req.pipe(gunzip);
|
|
source = gunzip;
|
|
}
|
|
source.on('data', function (chunk) {
|
|
chunks_1.push(chunk);
|
|
});
|
|
source.on('end', function () {
|
|
var xml = Buffer.concat(chunks_1).toString();
|
|
_this_1._processRequestXml(req, res, xml);
|
|
});
|
|
}
|
|
else {
|
|
res.end();
|
|
}
|
|
};
|
|
Server.prototype._process = function (input, req, res, cb) {
|
|
var _this_1 = this;
|
|
var pathname = url.parse(req.url).pathname.replace(/\/$/, '');
|
|
var obj = this.wsdl.xmlToObject(input);
|
|
var body = obj.Body;
|
|
var headers = obj.Header;
|
|
var binding;
|
|
var methodName;
|
|
var serviceName;
|
|
var portName;
|
|
var includeTimestamp = obj.Header && obj.Header.Security && obj.Header.Security.Timestamp;
|
|
var authenticate = this.authenticate || function defaultAuthenticate() { return true; };
|
|
var callback = function (result, statusCode) {
|
|
var response = { result: result };
|
|
_this_1.emit('response', response, methodName);
|
|
cb(response.result, statusCode);
|
|
};
|
|
var process = function () {
|
|
if (typeof _this_1.log === 'function') {
|
|
_this_1.log('info', 'Attempting to bind to ' + pathname);
|
|
}
|
|
// Avoid Cannot convert undefined or null to object due to Object.keys(body)
|
|
// and throw more meaningful error
|
|
if (!body) {
|
|
throw new Error('Failed to parse the SOAP Message body');
|
|
}
|
|
// use port.location and current url to find the right binding
|
|
binding = (function () {
|
|
var services = _this_1.wsdl.definitions.services;
|
|
var firstPort;
|
|
var name;
|
|
for (name in services) {
|
|
serviceName = name;
|
|
var service = services[serviceName];
|
|
var ports = service.ports;
|
|
for (name in ports) {
|
|
portName = name;
|
|
var port = ports[portName];
|
|
var portPathname = url.parse(port.location).pathname.replace(/\/$/, '');
|
|
if (typeof _this_1.log === 'function') {
|
|
_this_1.log('info', 'Trying ' + portName + ' from path ' + portPathname);
|
|
}
|
|
if (portPathname === pathname) {
|
|
return port.binding;
|
|
}
|
|
// The port path is almost always wrong for generated WSDLs
|
|
if (!firstPort) {
|
|
firstPort = port;
|
|
}
|
|
}
|
|
}
|
|
return !firstPort ? void 0 : firstPort.binding;
|
|
})();
|
|
if (!binding) {
|
|
throw new Error('Failed to bind to WSDL');
|
|
}
|
|
try {
|
|
if (binding.style === 'rpc') {
|
|
methodName = (Object.keys(body)[0] === 'attributes' ? Object.keys(body)[1] : Object.keys(body)[0]);
|
|
_this_1.emit('request', obj, methodName);
|
|
if (headers) {
|
|
_this_1.emit('headers', headers, methodName);
|
|
}
|
|
_this_1._executeMethod({
|
|
serviceName: serviceName,
|
|
portName: portName,
|
|
methodName: methodName,
|
|
outputName: methodName + 'Response',
|
|
args: body[methodName],
|
|
headers: headers,
|
|
style: 'rpc'
|
|
}, req, res, callback);
|
|
}
|
|
else {
|
|
var messageElemName = (Object.keys(body)[0] === 'attributes' ? Object.keys(body)[1] : Object.keys(body)[0]);
|
|
var pair = binding.topElements[messageElemName];
|
|
_this_1.emit('request', obj, pair.methodName);
|
|
if (headers) {
|
|
_this_1.emit('headers', headers, pair.methodName);
|
|
}
|
|
methodName = pair.methodName;
|
|
_this_1._executeMethod({
|
|
serviceName: serviceName,
|
|
portName: portName,
|
|
methodName: pair.methodName,
|
|
outputName: pair.outputName,
|
|
args: body[messageElemName],
|
|
headers: headers,
|
|
style: 'document'
|
|
}, req, res, callback, includeTimestamp);
|
|
}
|
|
}
|
|
catch (error) {
|
|
if (error.Fault !== undefined) {
|
|
return _this_1._sendError(error.Fault, callback, includeTimestamp);
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
// Authentication
|
|
if (typeof authenticate === 'function') {
|
|
var authResultProcessed_1 = false;
|
|
var processAuthResult_1 = function (authResult) {
|
|
if (authResultProcessed_1) {
|
|
return;
|
|
}
|
|
authResultProcessed_1 = true;
|
|
// Handle errors
|
|
if (authResult instanceof Error) {
|
|
return _this_1._sendError({
|
|
Code: {
|
|
Value: 'SOAP-ENV:Server',
|
|
Subcode: { value: 'InternalServerError' }
|
|
},
|
|
Reason: { Text: authResult.toString() },
|
|
statusCode: 500
|
|
}, callback, includeTimestamp);
|
|
}
|
|
// Handle actual results
|
|
if (typeof authResult === 'boolean') {
|
|
if (authResult === true) {
|
|
try {
|
|
process();
|
|
}
|
|
catch (error) {
|
|
if (error.Fault !== undefined) {
|
|
return _this_1._sendError(error.Fault, callback, includeTimestamp);
|
|
}
|
|
return _this_1._sendError({
|
|
Code: {
|
|
Value: 'SOAP-ENV:Server',
|
|
Subcode: { value: 'InternalServerError' }
|
|
},
|
|
Reason: { Text: error.toString() },
|
|
statusCode: 500
|
|
}, callback, includeTimestamp);
|
|
}
|
|
}
|
|
else {
|
|
return _this_1._sendError({
|
|
Code: {
|
|
Value: 'SOAP-ENV:Client',
|
|
Subcode: { value: 'AuthenticationFailure' }
|
|
},
|
|
Reason: { Text: 'Invalid username or password' },
|
|
statusCode: 401
|
|
}, callback, includeTimestamp);
|
|
}
|
|
}
|
|
};
|
|
var functionResult = authenticate(obj.Header && obj.Header.Security, processAuthResult_1, req, obj);
|
|
if (isPromiseLike(functionResult)) {
|
|
functionResult.then(function (result) {
|
|
processAuthResult_1(result);
|
|
}, function (err) {
|
|
processAuthResult_1(err);
|
|
});
|
|
}
|
|
if (typeof functionResult === 'boolean') {
|
|
processAuthResult_1(functionResult);
|
|
}
|
|
}
|
|
else {
|
|
throw new Error('Invalid authenticate function (not a function)');
|
|
}
|
|
};
|
|
Server.prototype._executeMethod = function (options, req, res, callback, includeTimestamp) {
|
|
var _this_1 = this;
|
|
options = options || {};
|
|
var method;
|
|
var body;
|
|
var headers;
|
|
var serviceName = options.serviceName;
|
|
var portName = options.portName;
|
|
var methodName = options.methodName;
|
|
var outputName = options.outputName;
|
|
var args = options.args;
|
|
var style = options.style;
|
|
if (this.soapHeaders) {
|
|
headers = this.soapHeaders.map(function (header) {
|
|
if (typeof header === 'function') {
|
|
return header(methodName, args, options.headers, req, res, _this_1);
|
|
}
|
|
else {
|
|
return header;
|
|
}
|
|
}).join('\n');
|
|
}
|
|
try {
|
|
method = this.services[serviceName][portName][methodName];
|
|
}
|
|
catch (error) {
|
|
return callback(this._envelope('', headers, includeTimestamp));
|
|
}
|
|
var handled = false;
|
|
var handleResult = function (error, result) {
|
|
if (handled) {
|
|
return;
|
|
}
|
|
handled = true;
|
|
if (error) {
|
|
if (error.Fault !== undefined) {
|
|
return _this_1._sendError(error.Fault, callback, includeTimestamp);
|
|
}
|
|
else {
|
|
return _this_1._sendError({
|
|
Code: {
|
|
Value: 'SOAP-ENV:Server',
|
|
Subcode: { value: 'InternalServerError' }
|
|
},
|
|
Reason: { Text: error.toString() },
|
|
statusCode: 500
|
|
}, callback, includeTimestamp);
|
|
}
|
|
}
|
|
if (style === 'rpc') {
|
|
body = _this_1.wsdl.objectToRpcXML(outputName, result, '', _this_1.wsdl.definitions.$targetNamespace);
|
|
}
|
|
else {
|
|
var element = _this_1.wsdl.definitions.services[serviceName].ports[portName].binding.methods[methodName].output;
|
|
body = _this_1.wsdl.objectToDocumentXML(outputName, result, element.targetNSAlias, element.targetNamespace);
|
|
}
|
|
callback(_this_1._envelope(body, headers, includeTimestamp));
|
|
};
|
|
if (!this.wsdl.definitions.services[serviceName].ports[portName].binding.methods[methodName].output) {
|
|
// no output defined = one-way operation so return empty response
|
|
handled = true;
|
|
body = '';
|
|
if (this.onewayOptions.emptyBody) {
|
|
body = this._envelope('', headers, includeTimestamp);
|
|
}
|
|
callback(body, this.onewayOptions.responseCode);
|
|
}
|
|
var methodCallback = function (error, result) {
|
|
if (error && error.Fault !== undefined) {
|
|
// do nothing
|
|
}
|
|
else if (result === undefined) {
|
|
// Backward compatibility to support one argument callback style
|
|
result = error;
|
|
error = null;
|
|
}
|
|
handleResult(error, result);
|
|
};
|
|
var result = method(args, methodCallback, options.headers, req, res, this);
|
|
if (typeof result !== 'undefined') {
|
|
if (isPromiseLike(result)) {
|
|
result.then(function (value) {
|
|
handleResult(null, value);
|
|
}, function (err) {
|
|
handleResult(err);
|
|
});
|
|
}
|
|
else {
|
|
handleResult(null, result);
|
|
}
|
|
}
|
|
};
|
|
Server.prototype._envelope = function (body, headers, includeTimestamp) {
|
|
var defs = this.wsdl.definitions;
|
|
var ns = defs.$targetNamespace;
|
|
var encoding = '';
|
|
var alias = utils_1.findPrefix(defs.xmlns, ns);
|
|
var envelopeDefinition = this.wsdl.options.forceSoap12Headers
|
|
? 'http://www.w3.org/2003/05/soap-envelope'
|
|
: 'http://schemas.xmlsoap.org/soap/envelope/';
|
|
var xml = '<?xml version="1.0" encoding="utf-8"?>' +
|
|
'<soap:Envelope xmlns:soap="' + envelopeDefinition + '" ' +
|
|
encoding +
|
|
this.wsdl.xmlnsInEnvelope + '>';
|
|
headers = headers || '';
|
|
if (includeTimestamp) {
|
|
var now = new Date();
|
|
var created = getDateString(now);
|
|
var expires = getDateString(new Date(now.getTime() + (1000 * 600)));
|
|
headers += '<o:Security soap:mustUnderstand="1" ' +
|
|
'xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" ' +
|
|
'xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' +
|
|
' <u:Timestamp u:Id="_0">' +
|
|
' <u:Created>' + created + '</u:Created>' +
|
|
' <u:Expires>' + expires + '</u:Expires>' +
|
|
' </u:Timestamp>' +
|
|
' </o:Security>\n';
|
|
}
|
|
if (headers !== '') {
|
|
xml += '<soap:Header>' + headers + '</soap:Header>';
|
|
}
|
|
xml += body ? '<soap:Body>' + body + '</soap:Body>' : '<soap:Body/>';
|
|
xml += '</soap:Envelope>';
|
|
return xml;
|
|
};
|
|
Server.prototype._sendError = function (soapFault, callback, includeTimestamp) {
|
|
var fault;
|
|
var statusCode;
|
|
if (soapFault.statusCode) {
|
|
statusCode = soapFault.statusCode;
|
|
soapFault.statusCode = undefined;
|
|
}
|
|
if ('faultcode' in soapFault) {
|
|
// Soap 1.1 error style
|
|
// Root element will be prependend with the soap NS
|
|
// It must match the NS defined in the Envelope (set by the _envelope method)
|
|
fault = this.wsdl.objectToDocumentXML('soap:Fault', soapFault, undefined);
|
|
}
|
|
else {
|
|
// Soap 1.2 error style.
|
|
// 3rd param is the NS prepended to all elements
|
|
// It must match the NS defined in the Envelope (set by the _envelope method)
|
|
fault = this.wsdl.objectToDocumentXML('Fault', soapFault, 'soap');
|
|
}
|
|
return callback(this._envelope(fault, '', includeTimestamp), statusCode);
|
|
};
|
|
Server.prototype._sendHttpResponse = function (res, statusCode, result) {
|
|
if (statusCode) {
|
|
res.statusCode = statusCode;
|
|
}
|
|
/*
|
|
* Calling res.write(result) follow by res.end() will cause Node.js to use
|
|
* chunked encoding, while calling res.end(result) directly will cause
|
|
* Node.js to calculate and send Content-Length header. See
|
|
* nodejs/node#26005.
|
|
*/
|
|
if (this.enableChunkedEncoding) {
|
|
res.write(result);
|
|
res.end();
|
|
}
|
|
else {
|
|
res.end(result);
|
|
}
|
|
};
|
|
return Server;
|
|
}(events_1.EventEmitter));
|
|
exports.Server = Server;
|
|
//# sourceMappingURL=server.js.map
|