/*******************************************************************************
 * Copyright (C) 2004-2006 Intel Corp. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  - Neither the name of Intel Corp. nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL Intel Corp. OR THE CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

/**
 * @author Anas Nashif
 * @author Eugene Yarmosh
 */

#ifdef HAVE_CONFIG_H
#include <wsman_config.h>
#endif

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "u/libu.h"
#include "wsman-xml-api.h"
#include "wsman-client-api.h"
#include "wsman-soap.h"
#include "wsman-xml.h"

#include "wsman-dispatcher.h"
#include "wsman-xml-serialize.h"
#include "wsman-faults.h"
#include "wsman-soap-envelope.h"


/**
 * @defgroup Dispatcher Dispatcher
 * @brief SOAP Dispatcher
 *
 * @{
 */

struct __MuHeaderInfo {
	char *ns;
	char *name;
};

static int is_mu_header(WsXmlNodeH header)
{
	int i;
	char *name, *ns;
	static struct __MuHeaderInfo s_Info[] = {
		{XML_NS_ADDRESSING, WSA_TO},
		{XML_NS_ADDRESSING, WSA_MESSAGE_ID},
		{XML_NS_ADDRESSING, WSA_RELATES_TO},
		{XML_NS_ADDRESSING, WSA_ACTION},
		{XML_NS_ADDRESSING, WSA_REPLY_TO},
		{XML_NS_ADDRESSING, WSA_FROM},
		{XML_NS_WS_MAN, WSM_RESOURCE_URI},
		{XML_NS_WS_MAN, WSM_SELECTOR_SET},
		{XML_NS_WS_MAN, WSM_MAX_ENVELOPE_SIZE},
		{XML_NS_WS_MAN, WSM_OPERATION_TIMEOUT},
		{XML_NS_WS_MAN, WSM_FRAGMENT_TRANSFER},
		{XML_NS_TRUST, WST_ISSUEDTOKENS},
		{NULL, NULL}
	};

	name = ws_xml_get_node_local_name(header);
	ns = ws_xml_get_node_name_ns(header);

	for (i = 0; s_Info[i].name != NULL; i++) {
		if ((ns == NULL && s_Info[i].ns == NULL) ||
		    (ns != NULL && s_Info[i].ns != NULL &&
		     !strcmp(ns, s_Info[i].ns))) {
			if (!strcmp(name, s_Info[i].name))
				return 1;
		}
	}
	debug("mustUnderstand: %s:%s", !ns ? "null" : ns, name ? name : "NULL");
	return 0;
}



static void
generate_op_fault(op_t * op,
			WsmanFaultCodeType faultCode,
			WsmanFaultDetailType faultDetail)
{
	if (op->out_doc) {
		ws_xml_destroy_doc(op->out_doc);
		op->out_doc = NULL;
	}
	if (op->in_doc == NULL) {
		return;
	}
	op->out_doc = wsman_generate_fault(op->in_doc, faultCode, faultDetail, NULL);
	return;
}

static void
generate_notunderstood_fault(op_t * op,
				   WsXmlNodeH notUnderstoodHeader)
{
	WsXmlNodeH child;
	WsXmlNodeH header;

	if (op->in_doc == NULL)
		return;
	generate_op_fault(op, SOAP_FAULT_MUSTUNDERSTAND, WSMAN_DETAIL_OK);

	if (op->out_doc != NULL) {
		header = ws_xml_get_soap_header(op->out_doc);
		if (header) {
			child = ws_xml_add_child(header, XML_NS_SOAP_1_2,
					     "NotUnderstood", NULL);
			ws_xml_add_qname_attr(child, NULL, "qname",
					      ws_xml_get_node_name_ns
					      (notUnderstoodHeader),
					      ws_xml_get_node_local_name
					      (notUnderstoodHeader));
		}
	} else {
		error("can't generate fault");
	}
	return;
}


static int check_for_duplicate_selectors(op_t * op)
{
	WsXmlNodeH header, node, selector;
	int retval = 0, index = 0;
	hash_t *h;

	header = wsman_get_soap_header_element( op->in_doc, NULL, NULL);
	if ((node = ws_xml_get_child(header, 0, XML_NS_WS_MAN,
			      WSM_SELECTOR_SET)) == NULL) {
		// No selectors
		return 0;
	}
	h = hash_create(HASHCOUNT_T_MAX, 0, 0);
	if (h == NULL) {
		generate_op_fault(op, WSMAN_INTERNAL_ERROR,
					OWSMAN_NO_DETAILS);
		error("could not create hash");
		return 1;
	}

	while ((selector = ws_xml_get_child(node, index++, XML_NS_WS_MAN,
				 WSM_SELECTOR))) {
		char *attrVal = ws_xml_find_attr_value(selector, NULL,
				WSM_NAME);
		if (!attrVal)
			continue;

		if (hash_lookup(h, attrVal)) {
			generate_op_fault(op, WSMAN_INVALID_SELECTORS,
						WSMAN_DETAIL_DUPLICATE_SELECTORS);
			debug("Selector %s duplicated", attrVal);
			retval = 1;
			break;
		}

		if (!hash_alloc_insert(h, attrVal,
				       ws_xml_get_node_text(selector))) {
			generate_op_fault(op, WSMAN_INTERNAL_ERROR,
						OWSMAN_NO_DETAILS);
			retval = 1;
			error("hash_alloc_insert failed");
			break;
		}
	}
	hash_free(h);
	return retval;
}

static int validate_control_headers(op_t * op)
{
	unsigned long size = 0;
	time_t duration;
	WsXmlNodeH header, child, maxsize;
	char *mu = NULL;

	header = wsman_get_soap_header_element( op->in_doc,  NULL, NULL);
	maxsize = ws_xml_get_child(header, 0, XML_NS_WS_MAN,
			     WSM_MAX_ENVELOPE_SIZE);
        /* DSP0226, v1.2
         * R13.1-3: A service should not send a SOAP Envelope with more than 32,767 octets unless the
         * client has specified a wsman:MaxEnvelopeSize header that overrides this limit
         */
        if (maxsize == NULL) { /* no wsman:MaxEnvelopeSize specified */
          op->maxsize = WSMAN_MAX_ENVELOPE_SIZE;
        }
        else {
          mu = ws_xml_find_attr_value(maxsize, XML_NS_SOAP_1_2,
				    SOAP_MUST_UNDERSTAND);
        }
	if (mu != NULL && strcmp(mu, "true") == 0) {
		size = ws_deserialize_uint32(NULL, header,
					     0, XML_NS_WS_MAN,
					     WSM_MAX_ENVELOPE_SIZE);
                /* wsman:MaxEnvelopeSize too small ? */
		if (size < WSMAN_MINIMAL_ENVELOPE_SIZE_REQUEST) {
			generate_op_fault(op, WSMAN_ENCODING_LIMIT,
						WSMAN_DETAIL_MINIMUM_ENVELOPE_LIMIT);
			return 0;
		}
		op->maxsize = size;
	}
	child = ws_xml_get_child(header, 0,
				 XML_NS_WS_MAN, WSM_OPERATION_TIMEOUT);
	if (child != NULL) {
		char *text = ws_xml_get_node_text(child);
		char *nsUri = ws_xml_get_node_name_ns(header);
		if (text == NULL ||
		    ws_deserialize_duration(text, &duration)) {
			generate_op_fault(op,
						WSA_INVALID_MESSAGE_INFORMATION_HEADER,
						WSMAN_DETAIL_OPERATION_TIMEOUT);
			return 0;
		}
		if (duration <= 0) {
			generate_op_fault(op, WSMAN_TIMED_OUT, WSMAN_DETAIL_OK);
			return 0;
		}
		op->expires = duration;
		// Not supported now
		if (ws_xml_find_attr_bool
		    (child, nsUri, SOAP_MUST_UNDERSTAND)) {
			generate_op_fault(op,
						WSMAN_UNSUPPORTED_FEATURE,
						WSMAN_DETAIL_OPERATION_TIMEOUT);
			return 0;
		}
	}
	return 1;
}


static WsXmlNodeH
validate_mustunderstand_headers(op_t * op)
{
	WsXmlNodeH child = NULL, header = NULL;
	int i;
	char *nsUri;

	header = wsman_get_soap_header_element(op->in_doc, NULL, NULL);
	nsUri = ws_xml_get_node_name_ns(header);

	for (i = 0;(child = ws_xml_get_child(header, i, NULL, NULL)) != NULL; i++) {
		if (ws_xml_find_attr_bool(child, nsUri, SOAP_MUST_UNDERSTAND)) {
			if (!is_mu_header(child)) {
				break;
			}
		}
	}

	if (child != NULL) {
		debug("Mustunderstand Fault: %s", ws_xml_get_node_text(child));
	}
	return child;
}

static int
check_supported_dialect(const char *dialect)
{
	if (strcmp(dialect, WSM_ASSOCIATION_FILTER_DIALECT) == 0)
		return 0;
	else if (strcmp(dialect, WSM_XPATH_FILTER_DIALECT) == 0)
		return 0;
	else if (strcmp(dialect, WSM_WQL_FILTER_DIALECT) == 0)
		return 0;
	else if (strcmp(dialect, WSM_CQL_FILTER_DIALECT) == 0)
		return 0;
	else if (strcmp(dialect, WSM_SELECTOR_FILTER_DIALECT) == 0)
		return 0;
	return 1;
}


/**
 * Check for duplicate Message ID
 * @param op operation
 * @return status
 */
static int
check_unsupported_features(op_t * op)
{
	WsXmlNodeH enumurate;
	WsXmlNodeH subscribe;
	WsXmlNodeH header = wsman_get_soap_header_element( op->in_doc, NULL, NULL);
	WsXmlNodeH body = ws_xml_get_soap_body(op->in_doc);
	int retVal = 0;
	WsXmlNodeH n, m, k;
	char *resource_uri = NULL, *mu = NULL;
	WsXmlAttrH attr = NULL;


	n = ws_xml_get_child(header, 0, XML_NS_ADDRESSING, WSA_FAULT_TO);
	if (n != NULL) {
		debug("wsa:FaultTo is not supported");
		retVal = 1;
		generate_op_fault(op, WSMAN_UNSUPPORTED_FEATURE,
					WSMAN_DETAIL_ADDRESSING_MODE);
		goto DONE;
	}
	n = ws_xml_get_child(header, 0, XML_NS_WS_MAN, WSM_LOCALE);
	if (n != NULL) {
		debug("Locale header found");
		mu = ws_xml_find_attr_value(n, XML_NS_SOAP_1_2,
					    SOAP_MUST_UNDERSTAND);
		if (mu != NULL && strcmp(mu, "true") == 0) {
			retVal = 1;
			generate_op_fault(op,
						WSMAN_UNSUPPORTED_FEATURE,
						WSMAN_DETAIL_LOCALE);
			goto DONE;
		}
	}
#if 0
	n = ws_xml_get_child(header, 0, XML_NS_WS_MAN,
			     WSM_FRAGMENT_TRANSFER);
	if (n != NULL) {
		debug("FragmentTransfer header found");
		mu = ws_xml_find_attr_value(n, XML_NS_SOAP_1_2,
					    SOAP_MUST_UNDERSTAND);
		if (mu != NULL && strcmp(mu, "true") == 0) {
			retVal = 1;
			generate_op_fault(op,
						WSMAN_UNSUPPORTED_FEATURE,
						WSMAN_DETAIL_FRAGMENT_LEVEL_ACCESS);
			goto DONE;
		}
	}
#endif
	enumurate = ws_xml_get_child(body, 0, XML_NS_ENUMERATION,
			     WSENUM_ENUMERATE);
	if (enumurate) {

	n = ws_xml_get_child(enumurate, 0, XML_NS_ENUMERATION,
			     WSENUM_END_TO);
	if (n != NULL) {
		retVal = 1;
		generate_op_fault(op, WSMAN_UNSUPPORTED_FEATURE,
					WSMAN_DETAIL_ADDRESSING_MODE);
	}
	n = ws_xml_get_child(enumurate, 0, XML_NS_ENUMERATION,
			     WSENUM_FILTER);
	m = ws_xml_get_child(enumurate, 0, XML_NS_WS_MAN, WSM_FILTER);
	if (n != NULL && m != NULL) {
		retVal = 1;
		generate_op_fault(op, WSEN_CANNOT_PROCESS_FILTER, WSMAN_DETAIL_OK);
		goto DONE;
	} else if (n || m) {
		char *dia = NULL;
		if (n) {
			dia = ws_xml_find_attr_value(n, NULL, WSM_DIALECT);
		} else if (m) {
			dia = ws_xml_find_attr_value(m, NULL, WSM_DIALECT);
		}
		if (dia)
			retVal = check_supported_dialect(dia);
		else
			retVal = check_supported_dialect (WSM_XPATH_FILTER_DIALECT);

		if (retVal) {
			retVal = 1;
			generate_op_fault(op, WSEN_FILTER_DIALECT_REQUESTED_UNAVAILABLE,
				WSMAN_DETAIL_OK);
			goto DONE;
			}
	}
	k = ws_xml_get_child(header, 0, XML_NS_WS_MAN, WSM_RESOURCE_URI);
	if (k)
		resource_uri = ws_xml_get_node_text(k);
	if (resource_uri &&
	    (strcmp(resource_uri, CIM_ALL_AVAILABLE_CLASSES) == 0)) {
		if (!n && !m) {
			retVal = 1;
			generate_op_fault(op, WSMAN_UNSUPPORTED_FEATURE,
						WSMAN_DETAIL_FILTERING_REQUIRED);
			goto DONE;
		}

	}
	}
	subscribe = ws_xml_get_child(body, 0, XML_NS_EVENTING, WSEVENT_SUBSCRIBE);
	if(subscribe) {
	/*	n = ws_xml_get_child(subscribe, 0, XML_NS_EVENTING, WSEVENT_ENDTO);
		if(n) {
			retVal = 1;
			generate_op_fault(op, WSMAN_UNSUPPORTED_FEATURE,
					WSMAN_DETAIL_ADDRESSING_MODE);
			goto DONE;
		}
	*/	n = ws_xml_get_child(subscribe, 0, XML_NS_EVENTING, WSEVENT_DELIVERY);
		if(n == NULL) {
			retVal = 1;
			generate_op_fault(op, WSE_INVALID_MESSAGE, WSMAN_DETAIL_OK);
			goto DONE;
		}
		attr = ws_xml_find_node_attr(n, NULL,WSEVENT_DELIVERY_MODE);
		if(attr) {
			mu = ws_xml_get_attr_value(attr);
			if (strcasecmp(mu, WSEVENT_DELIVERY_MODE_PUSH) &&
				strcasecmp(mu, WSEVENT_DELIVERY_MODE_PUSHWITHACK) &&
				strcasecmp(mu, WSEVENT_DELIVERY_MODE_EVENTS) &&
				strcasecmp(mu, WSEVENT_DELIVERY_MODE_PULL)) {
				debug("Unsupported delivery mode : %s",ws_xml_get_attr_value(attr));
				retVal = 1;
				generate_op_fault(op, WSE_DELIVERY_MODE_REQUESTED_UNAVAILABLE, WSMAN_DETAIL_OK);
				goto DONE;
			}
		}
		m = ws_xml_get_child(n, 0, XML_NS_WS_MAN, WSM_CONNECTIONRETRY);
		if(m) {
			retVal = 1;
			generate_op_fault(op, WSMAN_UNSUPPORTED_FEATURE, WSMAN_DETAIL_DELIVERY_RETRIES);
			goto DONE;
		}
	}
DONE:
	return retVal;
}



/**
 * Check for duplicate Message ID
 * @param op operation
 * @return status
 */
static int wsman_is_duplicate_message_id(op_t * op)
{
	WsXmlNodeH header = wsman_get_soap_header_element(op->in_doc, NULL, NULL);
	int retVal = 0;
	SoapH soap;
	WsXmlNodeH msgIdNode;
	soap = op->dispatch->soap;

	msgIdNode = ws_xml_get_child(header, 0, XML_NS_ADDRESSING, WSA_MESSAGE_ID);
	if (msgIdNode != NULL) {
		lnode_t *node;
		char *msgId;
		msgId = ws_xml_get_node_text(msgIdNode);
		if (msgId == NULL || msgId[0] == '\0' ) {
			generate_op_fault(op, WSA_INVALID_MESSAGE_INFORMATION_HEADER, WSMAN_DETAIL_OK);
			debug("MessageId missing");
			return 1;
		}
		debug("Checking Message ID: %s", msgId);
		u_lock(soap);

		if (soap->processedMsgIdList == NULL) {
			soap->processedMsgIdList = list_create(LISTCOUNT_T_MAX);
			if (!soap->processedMsgIdList) {
				error("list_create failed");
				return 1;
			}
		}
#ifndef IGNORE_DUPLICATE_ID
		node = list_first(soap->processedMsgIdList);
		while (node != NULL) {
			if (!strcmp(msgId, (char *) node->list_data)) {
				debug("Duplicate Message ID: %s", msgId);
				retVal = 1;
				generate_op_fault(op, WSA_INVALID_MESSAGE_INFORMATION_HEADER,
							WSA_DETAIL_DUPLICATE_MESSAGE_ID);
				break;
			}
			node = list_next(soap->processedMsgIdList, node);
		}
#endif


		if (!retVal) {
			while (list_count(soap->processedMsgIdList) >= PROCESSED_MSG_ID_MAX_SIZE) {
				node = list_del_first(soap->processedMsgIdList);
				u_free(node->list_data);
				u_free(node);
			}

			node = lnode_create(NULL);
			if (node) {
				node->list_data = u_str_clone(msgId);
				if (node->list_data == NULL) {
					u_free(node);
				} else {
					list_append(soap->processedMsgIdList, node);
				}
			}
		}
		u_unlock(soap);
	} else if (!wsman_is_identify_request(op->in_doc)) {
		generate_op_fault(op, WSA_MESSAGE_INFORMATION_HEADER_REQUIRED, WSMAN_DETAIL_OK);
		debug("No MessageId Header found");
		return 1;
	}

	return retVal;
}

int
soap_add_filter(SoapH soap,
		SoapServiceCallback callbackProc,
		void *callbackData, int inbound)
{
	callback_t *entry = NULL;

	if (soap) {
		if (!inbound) {
			if (!soap->outboundFilterList)
				soap->outboundFilterList = list_create(LISTCOUNT_T_MAX);
			entry = make_callback_entry(callbackProc, callbackData, soap->outboundFilterList);
		} else {
			if (!soap->inboundFilterList)
				soap->inboundFilterList = list_create(LISTCOUNT_T_MAX);
			entry = make_callback_entry(callbackProc, callbackData, soap->inboundFilterList);
		}
	}
	if (entry == NULL)
		return 0;
	else
		return 1;
}


int
outbound_control_header_filter(SoapOpH opHandle,
			       void *data, void *opaqueData)
{
	op_t *op = (op_t *)opHandle;
	WsmanMessage *msg = wsman_get_msg_from_op(opHandle);
	if(check_envelope_size(op->out_doc, op->maxsize, msg->charset)) {
		debug("****should not go here");
		generate_op_fault(op, WSMAN_ENCODING_LIMIT,
						WSMAN_DETAIL_SERVICE_ENVELOPE_LIMIT);
	}
	return 0;
}

int
outbound_addressing_filter(SoapOpH opHandle, void *data, void *opaqueData)
{
	WsXmlDocH in_doc = soap_get_op_doc(opHandle, 1);
	WsXmlDocH out_doc = soap_get_op_doc(opHandle, 0);

	WsXmlNodeH outHeaders = wsman_get_soap_header_element(out_doc, NULL, NULL);

	if (!outHeaders) {
		return 0;
	}
	if (ws_xml_get_child(outHeaders, 0, XML_NS_ADDRESSING,  WSA_MESSAGE_ID) == NULL &&
	    !wsman_is_identify_request(in_doc)) {
		char uuidBuf[100];
		generate_uuid(uuidBuf, sizeof(uuidBuf), 0);
		ws_xml_add_child(outHeaders, XML_NS_ADDRESSING,	 WSA_MESSAGE_ID, uuidBuf);
		debug("Adding message id: %s", uuidBuf);
	}
	if (in_doc != NULL) {
		WsXmlNodeH inMsgIdNode;
		inMsgIdNode = wsman_get_soap_header_element(in_doc,XML_NS_ADDRESSING,
							    WSA_MESSAGE_ID);
		if (inMsgIdNode != NULL &&
		    !ws_xml_get_child(outHeaders, 0, XML_NS_ADDRESSING, WSA_RELATES_TO)) {
			ws_xml_add_child(outHeaders, XML_NS_ADDRESSING, WSA_RELATES_TO,
					 ws_xml_get_node_text(inMsgIdNode));
		}
	}

	return 0;
}



static int
process_filter_chain(op_t *op, list_t *list, void *opaqueData)
{
	int retVal = 0;
	callback_t *filter = NULL;
	if (list != NULL) {
		filter = (callback_t *) list_first(list);
		while (!retVal && filter != NULL) {
			retVal = filter->proc((SoapOpH) op, filter->node.list_data,
					 opaqueData);
			filter = (callback_t *) list_next(list, &filter->node);
		}
	}
	return retVal;
}

/**
 * Process Filters
 * @param op SOAP operation
 * @param inbound Direction of message, 0 for outbound  and 1 for inbound.
 * @return 0 on success, 1 on error.
 **/
static int process_filters(op_t * op, int inbound, void *opaqueData)
{
	int retVal = 0;
	list_t *list;

	debug("Processing Filters: %s", (!inbound) ? "outbound" : "inbound");
	if (!(op->dispatch->flags & SOAP_SKIP_DEF_FILTERS)) {
		list = inbound ? op->dispatch->soap->inboundFilterList :
		    op->dispatch->soap->outboundFilterList;
		retVal = process_filter_chain(op, list, opaqueData);
	}
	if (retVal) {
		debug("process_filter_chain returned 1 for DEF FILTERS");
		return 1;
	}
	list = inbound ? op->dispatch->inboundFilterList :
	    op->dispatch->outboundFilterList;
	retVal = process_filter_chain(op, list, opaqueData);

	if (retVal) {
		debug("process_filter_chain returned 1");
		return 1;
	}

	if (inbound) {
		WsXmlNodeH notUnderstoodHeader;
		if (wsman_is_duplicate_message_id(op)) {
			debug("wsman_is_duplicate_message_id");
			return 1;
		}
		if (check_unsupported_features(op)) {
			debug("check_unsupported_features");
			return 1;
		}
		if ((notUnderstoodHeader =
		     validate_mustunderstand_headers(op)) != 0) {
			generate_notunderstood_fault(op, notUnderstoodHeader);
			debug("validate_mustunderstand_headers");
			return 1;
		}
		if (!validate_control_headers(op)) {
			debug("validate_control_headers");
			return 1;
		}
		if (check_for_duplicate_selectors(op)) {
			debug("check_for_duplicate_selectors");
			return 1;
		}
	}
	return 0;
}



static void
dispatcher_create_fault(SoapH soap, WsmanMessage * msg, WsXmlDocH in_doc)
{
	char *buf = NULL;
	int len = 0;
	if (!soap)
		return;

	if (wsman_fault_occured(msg)) {
		wsman_generate_fault_buffer(in_doc,
					    msg->charset,
					    msg->status.fault_code,
					    msg->status.fault_detail_code,
					    msg->status.fault_msg,
					    &buf, &len);
		if (buf) {
			u_buf_set(msg->response, buf, len);
			u_free(buf);
		}
		msg->http_code = wsman_find_httpcode_for_fault_code(
						msg->status.
					    fault_code);
	}
	return;
}


static int
process_inbound_operation(op_t * op, WsmanMessage * msg, void *opaqueData)
{
	int retVal = 1;
	char *buf = NULL;
	int len;

	msg->http_code = WSMAN_STATUS_OK;
	op->out_doc = NULL;
	if (op->dispatch->serviceCallback == NULL) {
		wsman_set_fault(msg, WSA_ACTION_NOT_SUPPORTED,
				OWSMAN_NO_DETAILS, NULL);
		debug("op service callback is null");
		goto GENERATE_FAULT;
	}

	if (process_filters(op, 1, opaqueData)) {
		if (op->out_doc == NULL) {
			error("doc is null");
			wsman_set_fault(msg, WSMAN_INTERNAL_ERROR,
					OWSMAN_NO_DETAILS, NULL);
			goto GENERATE_FAULT;
		}
		if (wsman_is_fault_envelope(op->out_doc)) {
			msg->http_code =
			    wsman_find_httpcode_for_value(op->out_doc);
		} else {
			error("not fault envelope");
		}

		ws_xml_dump_memory_enc(op->out_doc, &buf, &len, msg->charset);
		u_buf_set(msg->response, buf, len);
		ws_xml_destroy_doc(op->out_doc);
		op->out_doc = NULL;
		u_free(buf);
		return 1;
	}


	retVal = op->dispatch->serviceCallback((SoapOpH) op,
					  op->dispatch->serviceData,
					  opaqueData);
	if (op->out_doc == NULL) {
		// XXX (correct fault?)
		wsman_set_fault(msg, WSA_DESTINATION_UNREACHABLE,
				WSMAN_DETAIL_INVALID_RESOURCEURI, NULL);
		error("output doc is null");
		goto GENERATE_FAULT;
	}

	process_filters(op, 0, opaqueData);
	if (op->out_doc == NULL) {
		error("doc is null");
		wsman_set_fault(msg, WSMAN_INTERNAL_ERROR,
				OWSMAN_NO_DETAILS, NULL);
		goto GENERATE_FAULT;
	}
	if (wsman_is_fault_envelope(op->out_doc)) {
		msg->http_code =
		    wsman_find_httpcode_for_value(op->out_doc);
	}
	else {
		wsman_add_fragement_for_header(op->in_doc, op->out_doc);
	}
	ws_xml_dump_memory_enc(op->out_doc, &buf, &len, msg->charset);
	u_buf_set(msg->response, buf, len);
	ws_xml_destroy_doc(op->out_doc);
	op->out_doc = NULL;
	u_free(buf);
	return 0;

      GENERATE_FAULT:
	return retVal;
}


static SoapDispatchH get_dispatch_entry(SoapH soap, WsXmlDocH doc)
{
	SoapDispatchH dispatch = NULL;
	if (soap->dispatcherProc) {
		dispatch = soap->dispatcherProc(soap->cntx,
						soap->dispatcherData, doc);
	}
	if (dispatch == NULL) {
		error("No dispatcher found for request");
	} else {
		dispatch->usageCount++;
	}
	return dispatch;
}

void
dispatch_inbound_call(SoapH soap, WsmanMessage * msg, void *opaqueData)
{
	op_t *op = NULL;
	WsXmlDocH in_doc = wsman_build_inbound_envelope( msg);
	SoapDispatchH dispatch = NULL;
	debug("Inbound call...");
#if 0
        /* debug incoming message */
        int size;
        char *buf;
        ws_xml_dump_memory_node_tree_enc( ws_xml_get_soap_body(in_doc), &buf, &size, "UTF-8" );  
	debug(buf);
#endif
	if (wsman_fault_occured(msg)) {
		error("document is null");
		goto DONE;
	}
	dispatch = get_dispatch_entry(soap, in_doc);

	if (dispatch == NULL) {
		wsman_set_fault(msg, WSA_DESTINATION_UNREACHABLE,
				WSMAN_DETAIL_INVALID_RESOURCEURI, NULL);
		debug("dispatch == NULL");
		goto DONE;
	}
	op = create_op_entry(soap, dispatch, msg);
	if (op == NULL) {
		wsman_set_fault(msg, WSA_DESTINATION_UNREACHABLE,
				WSMAN_DETAIL_INVALID_RESOURCEURI, NULL);
		destroy_dispatch_entry(dispatch);
		debug("op entry == NULL");
		goto DONE;
	}
	op->in_doc = in_doc;
	process_inbound_operation(op, msg, opaqueData);
DONE:
	dispatcher_create_fault(soap, msg, in_doc);
	destroy_op_entry(op);
	ws_xml_destroy_doc(in_doc);
	debug("Inbound call completed");
	return;
}


static char *wsman_dispatcher_match_ns(WsDispatchInterfaceInfo * r,
		char *uri)
{
	char *ns = NULL;
	lnode_t *node = NULL;
	if (r->namespaces == NULL) {
		return NULL;
	}
	if (!uri)
		return NULL;

	node = list_first(r->namespaces);
	while (node) {
		WsSupportedNamespaces *sns =
		    (WsSupportedNamespaces *) node->list_data;
		debug("namespace: %s", sns->ns);
		if (sns->ns != NULL && strstr(uri, sns->ns)) {
			ns = u_strdup(sns->ns);
			break;
		}
		node = list_next(r->namespaces, node);
	}
	return ns;
}


WsEndPointRelease
wsman_get_release_endpoint(WsContextH cntx, WsXmlDocH doc)
{
	WsManDispatcherInfo *dispInfo =
	    (WsManDispatcherInfo *) cntx->soap->dispatcherData;
	lnode_t *node = NULL;
	WsDispatchInterfaceInfo *r = NULL;
	WsDispatchEndPointInfo *ep = NULL;
	char *ptr = ENUM_ACTION_RELEASE, *ns = NULL, *uri = NULL;
	int i;

	if (dispInfo->interfaces) {
		node = list_first((list_t *) dispInfo->interfaces);
	}
	uri = wsman_get_resource_uri(cntx, doc);

	while (node != NULL) {
		WsDispatchInterfaceInfo *ifc =
		    (WsDispatchInterfaceInfo *) node->list_data;
		if (ifc->wsmanResourceUri == NULL &&
		    (ns = wsman_dispatcher_match_ns(ifc, uri))) {
			r = ifc;
			break;
		}
		if (uri && ifc->wsmanResourceUri &&
		    !strcmp(uri, ifc->wsmanResourceUri)) {
			r = ifc;
			break;
		}
		node = list_next((list_t *) dispInfo->interfaces, node);
	}
	if (r == NULL) {
		u_free(ns);
		return NULL;
	}
	/*
	 * See if the action is part of the namespace which means that
	 * we are dealing with a custom action
	 */
	if (ns != NULL) {
		size_t len = strlen(ns);
		if (!strncmp(ptr, ns, len) && ptr[len] == '/') {
			ptr += len + 1;
		}
	}
	for (i = 0; r->endPoints[i].serviceEndPoint != NULL; i++) {
		if (r->endPoints[i].inAction != NULL &&
		    !strcmp(ptr, r->endPoints[i].inAction)) {
			ep = &r->endPoints[i];
			break;
		}
	}
	u_free(ns);

	if (ep == NULL) {
		debug("no ep");
		return NULL;
	}
	debug("Release endpoint: %p", ep->serviceEndPoint);
	return (WsEndPointRelease) ep->serviceEndPoint;
}

SoapDispatchH wsman_dispatcher(WsContextH cntx, void *data, WsXmlDocH doc)
{
	SoapDispatchH disp = NULL;
	char *uri = NULL, *action;
	WsManDispatcherInfo *dispInfo = (WsManDispatcherInfo *) data;
	WsDispatchEndPointInfo *ep = NULL;
	WsDispatchEndPointInfo *ep_custom = NULL;

	WsXmlDocH notdoc = NULL;

#ifdef ENABLE_EVENTING_SUPPORT
	WsXmlNodeH nodedoc = NULL;
#endif
	int i;
	char *ns = NULL;

	WsDispatchInterfaceInfo *r = NULL;
	lnode_t *node = list_first((list_t *) dispInfo->interfaces);

	if (doc == NULL) {
		error("doc is null");
		u_free(data);
		goto cleanup;
	}
	uri = wsman_get_resource_uri(cntx, doc);
	action = wsman_get_action(cntx, doc);
#ifdef ENABLE_EVENTING_SUPPORT
	if(wsman_is_event_related_request(doc)) {
		WsXmlNodeH temp = ws_xml_get_child( ws_xml_get_soap_header(doc), 0,
			XML_NS_EVENTING, WSEVENT_IDENTIFIER);
		char *uuid = ws_xml_get_node_text(temp);
		debug("Request uuid: %s", uuid ? uuid : "NULL");
		if(uuid) {
			lnode_t *t = list_first(cntx->subscriptionMemList);
			while(t != NULL) {
				WsSubscribeInfo *subsInfo = (WsSubscribeInfo *)t->list_data;
				if(!strcmp(uuid+5, subsInfo->subsId)) {
					uri = subsInfo->uri;
					break;
				}
				else
					t = list_next(cntx->subscriptionMemList, t);
			}
			if(t == NULL) {
				unsigned char *buf = NULL;
				int len;
				if(cntx->soap->subscriptionOpSet->get_subscription(cntx->soap->uri_subsRepository,
					uuid+5, &buf, &len) == 0) {
					notdoc = ws_xml_read_memory( (char *)buf, len, "UTF-8", 0);
					if(notdoc) {
						nodedoc = ws_xml_get_soap_header(notdoc);
						if(nodedoc) {
							nodedoc = ws_xml_get_child(nodedoc, 0, XML_NS_WS_MAN, WSM_RESOURCE_URI);
							if(nodedoc) {
								uri = ws_xml_get_node_text(nodedoc);
							}
						}
					}
					u_free(buf);
				}
			}
		}
	}
#endif
	debug("uri: %s, action: %s", uri ? uri : "NULL", action ? action : "NULL");
	if ((!uri || !action) && !wsman_is_identify_request(doc)) {
		goto cleanup;
	}
	while (node != NULL) {

		WsDispatchInterfaceInfo *ifc = (WsDispatchInterfaceInfo *) node->list_data;
		if (wsman_is_identify_request(doc)) {
			if ((ns = wsman_dispatcher_match_ns(ifc, XML_NS_WSMAN_ID))) {
				r = ifc;
				break;
			}
			debug("ns did not match");
		}
		/*
		 * If Resource URI is null then most likely we are dealing
		 * with  a generic plugin supporting a namespace with
		 * multiple Resource URIs (e.g. CIM)
		 **/
		else {
			if (!uri) {
				goto cleanup;
			}
			if (ifc->wsmanResourceUri == NULL &&
			    (ns = wsman_dispatcher_match_ns(ifc, uri))) {
				r = ifc;
				break;
			} else if (ifc->wsmanResourceUri &&
				   !strcmp(uri, ifc->wsmanResourceUri)) {
				r = ifc;
				break;
			}
		}
		node = list_next((list_t *) dispInfo->interfaces, node);
	}


	if (wsman_is_identify_request(doc) && r != NULL) {
		ep = &r->endPoints[0];
	} else if (r != NULL) {
		if (!action) {
			goto cleanup;
		}
		char *ptr = action;
		/*
		 * See if the action is part of the namespace which means that
		 * we are dealing with a custom action
		 */
		if (ns != NULL) {
			size_t len = strlen(ns);
			if (!strncmp(action, ns, len) &&
			    action[len] == '/')
				ptr = &action[len + 1];
		}
		for (i = 0; r->endPoints[i].serviceEndPoint != NULL; i++) {
			if (r->endPoints[i].inAction != NULL &&
			    !strcmp(ptr, r->endPoints[i].inAction)) {
				ep = &r->endPoints[i];
				break;
			} else if (r->endPoints[i].inAction == NULL) {
				/*
				 * Just store it for later
				 * in case no match is found for above condition
				 */
				ep_custom = &r->endPoints[i];
			}
		}
	}
	ws_remove_context_val(cntx, WSM_RESOURCE_URI);

	if (ep != NULL) {
		for (i = 0; i < dispInfo->mapCount; i++) {
			if (dispInfo->map[i].ep == ep) {
				disp = dispInfo->map[i].disp;
				break;
			}
		}
	} else if (ep_custom != NULL) {
		for (i = 0; i < dispInfo->mapCount; i++) {
			if (dispInfo->map[i].ep == ep_custom) {
				disp = dispInfo->map[i].disp;
				break;
			}
		}
	}

cleanup:
	if(notdoc)
		ws_xml_destroy_doc(notdoc);
	if (ns)
		u_free(ns);
	return disp;
}


/*
 * Create dispatch Entry
 *
 * @param soap Soap Framework Handle
 * @param inboundAction Inbound Action
 * @param outboundAction Outbound Action
 * @param role Role
 * @param proc Call back processor
 * @param data Callback Data
 * @param flags Flags
 * @return Dispatch Entry
 */
static SoapDispatchH
create_dispatch_entry(SoapH soap,
		      char *inboundAction,
		      char *outboundAction,
		      char *role,
		      SoapServiceCallback proc,
		      void *data, unsigned long flags)
{

	SoapDispatchH entry = wsman_dispatch_entry_new();
	if (entry) {
		entry->soap = soap;
		entry->flags = flags;
		entry->inboundAction = u_str_clone(inboundAction);
		entry->outboundAction = u_str_clone(outboundAction);
		entry->serviceCallback = proc;
		entry->serviceData = data;
		entry->usageCount = 1;
	}
	return entry;
}



/*
 * Create Dispatch Entry
 * @param soap Soap handle
 * @param inboundAction Inbound Action
 * @param outboundAction Outbound Action (optional)
 * @param role Role (reserved, must be NULL)
 * @param callbackProc Callback processor
 * @param callbackData Callback data
 * @param flags Flags
 * @return Dispatch Handle
 */
SoapDispatchH wsman_dispatch_create(SoapH soap,
		char *inboundAction, char *outboundAction,	//optional
		char *role,	//reserved, must be NULL
		SoapServiceCallback callbackProc,
		void *callbackData,
		unsigned long flags)
{

	SoapDispatchH disp = NULL;
	if (soap && role == NULL) {
		disp = create_dispatch_entry(soap, inboundAction,
					  outboundAction, role,
					  callbackProc, callbackData,
					  flags);
	}
	return disp;
}

/*
 * Start Dispatcher
 */
void wsman_dispatch_start(SoapDispatchH disp)
{
	list_t *displist = NULL;
	if (disp) {
		displist = disp->soap->dispatchList;
		if (displist != NULL || ( displist = list_create(LISTCOUNT_T_MAX) )) {
			list_append(displist, &(disp)->node);
		}
	}
}


SoapDispatchH wsman_dispatch_entry_new(void)
{
	return (SoapDispatchH) u_zalloc(sizeof(struct __dispatch_t));
}



/** @} */
