

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration 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 its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  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 THE COPYRIGHT OWNER 
//_/_/  OR 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.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file RESTAPI.cpp
	@brief Implementation of RESTParser, RESTRequest and RESTHTTPCall (see RESTAPI.h).

	Parses the XML REST API definition, validates incoming HTTP requests or
	DataMessages against declared parameter sets (including nested ARRAY objects
	via JSON/jsmn and multipart content), and builds outbound HTTP calls.
*/
#include "RESTAPI.h"
#include "UnitTestFramework.h"

namespace cmlabs {

RESTParser::RESTParser() {
	mainNode = XMLNode::emptyXMLNode;
}
RESTParser::~RESTParser() {
	std::map<std::string, RESTHTTPCall*>::iterator i = httpCalls.begin(), e = httpCalls.end();
	while (i != e) {
		delete(i->second);
		i++;
	}
	httpCalls.clear();
}


bool RESTParser::init(const char* restDefinition) {

	lastErrorString.clear();
	XMLResults xmlResults;
	mainNode=XMLNode::parseString(restDefinition,"restapi", &xmlResults);

	if (xmlResults.error != eXMLErrorNone) {
		if (xmlResults.error == eXMLErrorFirstTagNotFound) {
			lastErrorString = utils::StringFormat("REST API Definition format error, main tag <restapi> not found");
		}
		else {
			lastErrorString = utils::StringFormat("REST API Definition parsing error at line %i, column %i:\n   %s\n",
				xmlResults.nLine,xmlResults.nColumn, XMLNode::getError(xmlResults.error));
		}
		mainNode = XMLNode::emptyXMLNode;
		return false;
	}
	if(mainNode.isEmpty()) {
		lastErrorString = utils::StringFormat("REST API Definition format error, main tag <restapi> empty");
		mainNode = XMLNode::emptyXMLNode;
		return false;
	}

	// read any defined objects
	std::map<std::string, RESTParam> paramMap;
	XMLNode node;
	const char* name;
	uint32 i, count = mainNode.nChildNode("paramset");
	for (i = 0; i < count; i++) {
		if (!(node = mainNode.getChildNode("paramset", i)).isEmpty()) {
			if (!(name = node.getAttribute("name")) || !strlen(name))
				continue;
			paramMap = parseParamSet(node);
			if (paramMap.size())
				paramSets[name] = paramMap;
		}
	}

	// read any defined HTTPCalls
	RESTHTTPCall* httpCall;
	count = mainNode.nChildNode("httpcall");
	for (i = 0; i < count; i++) {
		if (!(node = mainNode.getChildNode("httpcall", i)).isEmpty()) {
			if (!(name = node.getAttribute("name")) || !strlen(name))
				continue;
			httpCall = new RESTHTTPCall();
			if (!httpCall->init(node)) {
				lastErrorString = utils::StringFormat("REST API Definition format error, <httpcall> tag name or url error: %s", name);
				delete(httpCall);
				break;
			}
			httpCalls[name] = httpCall;
		}
	}

	return (lastErrorString.length() == 0);
}


std::map<std::string, RESTParam> RESTParser::parseParamSet(XMLNode objectNode) {
	std::map<std::string, RESTParam> paramMap;
	const char* optional, *type, *name, *objects, *allowEmpty;
	RESTParam restParam;
	XMLNode node;
	uint32 i, count = objectNode.nChildNode("param");
	for (i = 0; i < count; i++) {
		if (!(node = objectNode.getChildNode("param", i)).isEmpty()) {
			type = node.getAttribute("type");
			name = node.getAttribute("name");
			optional = node.getAttribute("optional");
			allowEmpty = node.getAttribute("allowempty");
			objects = node.getAttribute("objects");

			if (!type || !name || !strlen(name)) {
				lastErrorString = utils::StringFormat("REST API Definition format error, <param> tag name or type error: %s", name);
				break;
			}

			if (strcmp(type, "integer") == 0) restParam.type = RESTParam::INTEGER;
			else if (strcmp(type, "text") == 0) restParam.type = RESTParam::TEXT;
			else if (strcmp(type, "float") == 0) restParam.type = RESTParam::FLOAT;
			else if (strcmp(type, "double") == 0) restParam.type = RESTParam::FLOAT;
			else if (strcmp(type, "array") == 0) restParam.type = RESTParam::ARRAY;
			else if (strcmp(type, "image") == 0) restParam.type = RESTParam::IMAGE;
			else if (strcmp(type, "video") == 0) restParam.type = RESTParam::VIDEO;
			else if (strcmp(type, "binary") == 0) restParam.type = RESTParam::BINARY;
			else {
				lastErrorString = utils::StringFormat("REST API Definition format error, <param> type error: %s", type);
				break;
			}

			restParam.optional = false | (optional && strcmp(optional, "yes") == 0);
			restParam.allowEmpty = false | (allowEmpty && strcmp(allowEmpty, "yes") == 0);
			if (objects && strlen(objects)) restParam.objects = objects; else restParam.objects.clear();
			restParam.name = name;
			paramMap[restParam.name] = restParam;
		}
	}
	return paramMap;
}

RESTRequest* RESTParser::parseRequest(HTTPRequest* req) {
	RESTRequest* restReq = new RESTRequest();
	std::map<std::string, RESTParam> paramSet;
	std::string signature;

//	XMLNode node = parseRequest(req->getRequest(), paramSet, restReq, signature);
	XMLNode node = parseRequest(req->getURI(), paramSet, restReq, signature);
	if (node.isEmpty())
		return restReq;

	const char* auth;
	if (restReq->getRequireAuthentication()) {
		if (!(auth = req->getHeaderEntry("Authorization")) || !strlen(auth)) {
			restReq->addToErrorString(utils::StringFormat("No authentication provided, required for this operation").c_str());
			restReq->setStatus(RESTRequest::FAILED_AUTH);
			return restReq;
		}
		restReq->setString("Authentication", auth);
	}

	const char* httpCallName = node.getAttribute("httpcall");
	if (httpCallName && strlen(httpCallName)) {
		std::map<std::string, RESTHTTPCall*>::iterator iHC = httpCalls.find(httpCallName);
		if (iHC != httpCalls.end()) {
			restReq->httpCall = iHC->second;
		}
	}

	// Now the node is the one at the correct level
	// and if an id we have the id (int or text) in the restReq already
	const char* ops = node.getAttribute("ops");

	if ( (req->type == HTTP_GET) && (utils::stristr(ops, "get")) ) {
		// Dont expect any more parameters, we are fine now
		signature = utils::StringFormat("GET%s", signature.c_str());
		restReq->setRequestSignature(signature.c_str());
		restReq->setStatus(RESTRequest::SUCCESS);
		return restReq;
	}
	else if ( (req->type == HTTP_POST) && (utils::stristr(ops, "post")) ) {
		// A post needs the parameters described in paramSet, check if they are there
		if (!extractParameters(req, paramSet, restReq)) {
			restReq->setStatus(RESTRequest::FAILED_PARAM);
			return restReq;
		}
		signature = utils::StringFormat("POST%s", signature.c_str());
		restReq->setRequestSignature(signature.c_str());
		restReq->setStatus(RESTRequest::SUCCESS);
		return restReq;
	}
	else if ( (req->type == HTTP_PUT) && (utils::stristr(ops, "put")) ) {
		// A put needs an ID (already there) and the parameters described in paramSet, check if they are there
		if (!extractParameters(req, paramSet, restReq)) {
			// If they are not all there, that's ok, only the updating ones need be there
			//restReq->setStatus(RESTRequest::FAILED_PARAM);
			//return restReq;
		}
		signature = utils::StringFormat("PUT%s", signature.c_str());
		restReq->setRequestSignature(signature.c_str());
		restReq->setStatus(RESTRequest::SUCCESS);
		return restReq;
	}
	else if ( (req->type == HTTP_DELETE) && (utils::stristr(ops, "delete")) ) {
		// Dont expect any more parameters, we are fine now
		signature = utils::StringFormat("DELETE%s", signature.c_str());
		restReq->setRequestSignature(signature.c_str());
		restReq->setStatus(RESTRequest::SUCCESS);
		return restReq;
	}
	else if (req->type == HTTP_OPTIONS) {
		//restReq->addToErrorString(utils::StringFormat("Operation type %s not supported by the requested API", HTTP_Type[req->type]).c_str());
		signature = ops;
		restReq->setStatus(RESTRequest::SUCCESS_OPTIONS);
		return restReq;
	}
	else {
		restReq->addToErrorString(utils::StringFormat("Operation type %s not supported by the requested API", HTTP_Type[req->type]).c_str());
		restReq->setStatus(RESTRequest::FAILED_FORMAT);
		return restReq;
	}
}

RESTRequest* RESTParser::parseRequest(DataMessage* msg) {
	RESTRequest* restReq = new RESTRequest();
	std::map<std::string, RESTParam> paramSet;
	std::string signature;

//	XMLNode node = parseRequest(msg->getString("REQUEST"), paramSet, restReq, signature);
	XMLNode node = parseRequest(msg->getString("URI"), paramSet, restReq, signature);
	if (node.isEmpty())
		return restReq;

	const char* httpCallName = node.getAttribute("httpcall");
	if (httpCallName && strlen(httpCallName)) {
		std::map<std::string, RESTHTTPCall*>::iterator iHC = httpCalls.find(httpCallName);
		if (iHC != httpCalls.end()) {
			restReq->httpCall = iHC->second;
		}
	}

	int64 httpOperation = 0;
	msg->getInt("HTTP_OPERATION", httpOperation);

	const char* auth;
	if (restReq->getRequireAuthentication()) {
		if (!(auth = msg->getString("Authorization")) || !strlen(auth)) {
			restReq->addToErrorString(utils::StringFormat("No authentication provided, required for this operation").c_str());
			restReq->setStatus(RESTRequest::FAILED_AUTH);
			return restReq;
		}
		restReq->setString("Authentication", auth);
	}

	// Now the node is the one at the correct level
	// and if an id we have the id (int or text) in the restReq already
	const char* ops = node.getAttribute("ops");

	if ( (httpOperation == HTTP_GET) && (utils::stristr(ops, "get")) ) {
		// Dont expect any more parameters, we are fine now
		signature = utils::StringFormat("GET%s", signature.c_str());
		restReq->setRequestSignature(signature.c_str());
		restReq->setStatus(RESTRequest::SUCCESS);
		return restReq;
	}
	else if ( (httpOperation == HTTP_POST) && (utils::stristr(ops, "post")) ) {
		// A post needs the parameters described in paramSet, check if they are there
		if (!extractParameters(msg, paramSet, restReq)) {
			restReq->setStatus(RESTRequest::FAILED_PARAM);
			return restReq;
		}
		signature = utils::StringFormat("POST%s", signature.c_str());
		restReq->setRequestSignature(signature.c_str());
		restReq->setStatus(RESTRequest::SUCCESS);
		return restReq;
	}
	else if ( (httpOperation == HTTP_PUT) && (utils::stristr(ops, "put")) ) {
		// A put needs an ID (already there) and the parameters described in paramSet, check if they are there
		if (!extractParameters(msg, paramSet, restReq)) {
			// If they are not all there, that's ok, only the updating ones need be there
			//restReq->setStatus(RESTRequest::FAILED_PARAM);
			//return restReq;
		}
		signature = utils::StringFormat("PUT%s", signature.c_str());
		restReq->setRequestSignature(signature.c_str());
		restReq->setStatus(RESTRequest::SUCCESS);
		return restReq;
	}
	else if ( (httpOperation == HTTP_DELETE) && (utils::stristr(ops, "delete")) ) {
		// Dont expect any more parameters, we are fine now
		signature = utils::StringFormat("DELETE%s", signature.c_str());
		restReq->setRequestSignature(signature.c_str());
		restReq->setStatus(RESTRequest::SUCCESS);
		return restReq;
	}
	else if (httpOperation == HTTP_OPTIONS) {
		//restReq->addToErrorString(utils::StringFormat("Operation type %s not supported by the requested API", HTTP_Type[req->type]).c_str());
		restReq->setRequestSignature(ops);
		restReq->setStatus(RESTRequest::SUCCESS_OPTIONS);
		return restReq;
	}
	else {
		restReq->addToErrorString(utils::StringFormat("Operation type %s not supported by the requested API", HTTP_Type[httpOperation]).c_str());
		restReq->setStatus(RESTRequest::FAILED_FORMAT);
		return restReq;
	}
}





XMLNode RESTParser::parseRequest(const char* requestString, std::map<std::string, RESTParam> &paramSet, RESTRequest* restReq, std::string &signature) {

	XMLNode emptyNode = XMLNode::emptyNode();

	if (mainNode.isEmpty()) {
		restReq->addToErrorString(utils::StringFormat("API not initialised").c_str());
		restReq->setStatus(RESTRequest::FAILED_DESCRIPTION);
		return emptyNode;
	}

	if (!requestString || !strlen(requestString)) {
		restReq->addToErrorString(utils::StringFormat("Empty request provided").c_str());
		restReq->setStatus(RESTRequest::FAILED_EMPTY);
		return emptyNode;
	}

	std::string filterString;
	std::string requestPart;
	const char* filterDiv = strstr(requestString, "?");
	if (filterDiv) {
		filterString = filterDiv+1;
		requestPart = utils::TextListSplit(requestString, "?", true, true).at(0);
	}
	else
		requestPart = requestString;

	std::vector<std::string> reqParts = utils::TextListSplit(requestPart.c_str(), "/", false, true);

	XMLNode topNode = findTopNodeForRequest(reqParts.at(0).c_str());
	if (topNode.isEmpty()) {
		restReq->addToErrorString(utils::StringFormat("Requested API is not known: %s", reqParts.at(0).c_str()).c_str());
		restReq->setStatus(RESTRequest::FAILED_UNKNOWN_REQUEST);
		return emptyNode;
	}

	if (filterString.length()) {
		std::multimap<std::string, std::string> paramMap = utils::TextMultiMapSplit(filterString.c_str(), "&", "=");
		std::multimap<std::string, std::string>::iterator ip = paramMap.begin(), ep = paramMap.end();
		while (ip != ep) {
			//restReq->setFilter(ip->first.c_str(), ip->second.c_str());
			restReq->setFilter(utils::TextTrimQuotes(html::DecodeHTML(ip->first).c_str()).c_str(), utils::TextTrimQuotes(html::DecodeHTML(ip->second).c_str()).c_str());
			ip++;
		}
	}

	const char* tag, *type, *name, *subName, *set, *auth, *subapi;
	XMLNode node = topNode, subNode;
	uint32 level = 0;
	int subNodeCount = 0, i;

	//signature.clear();

	while (level < reqParts.size()) {
		// first read any params available
		if (!(name = node.getAttribute("name")) || !strlen(name)) {
			restReq->addToErrorString(utils::StringFormat("API description error: Node name not provided").c_str());
			restReq->setStatus(RESTRequest::FAILED_DESCRIPTION);
			return emptyNode;
		}

		if (set = node.getAttribute("paramset")) {
			if (!strlen(set))
				paramSet.clear();
			else if (paramSets.find(set) != paramSets.end())
				paramSet = paramSets[set];
			else {
				restReq->addToErrorString(utils::StringFormat("API description error: Paramset %s unknown", set).c_str());
				restReq->setStatus(RESTRequest::FAILED_DESCRIPTION);
				return emptyNode;
			}
		}

		if ((auth = node.getAttribute("auth")) && stricmp(auth, "yes") == 0)
			restReq->setRequireAuthentication(true);

		if (!(tag = node.getName()) || !strlen(tag)) {
			restReq->addToErrorString(utils::StringFormat("API description error: Empty tag name").c_str());
			restReq->setStatus(RESTRequest::FAILED_DESCRIPTION);
			return emptyNode;
		}

		if (strcmp(tag, "id") == 0) {
			if (type = node.getAttribute("type")) {
				if (strcmp(type, "integer") == 0) {
					restReq->setInt(name, utils::Ascii2Int64(reqParts.at(level).c_str()));
					signature = utils::StringFormat("%s_IDINTEGER", signature.c_str());
				}
				else if (strcmp(type, "text") == 0) {
					restReq->setString(name, reqParts.at(level).c_str());
					signature = utils::StringFormat("%s_IDTEXT", signature.c_str());
				}
			}
		}
		else if (strcmp(tag, "api") == 0) {
			signature = utils::StringFormat("%s_%s", signature.c_str(), reqParts.at(level).c_str());
		}

		// Check for subapi nodes
		// For now we assume subapi="*", may in the future restrict to list of subapis
		if ((subapi = node.getAttribute("subapi")) && strlen(subapi)) {
			// create a new request with just the sub part from here
			std::string newRequest;
			std::string oldSignature = signature;
			for (uint32 n=level+1; n<reqParts.size(); n++) {
				if (n == level+1)
					newRequest = reqParts.at(n);
				else
					newRequest = utils::StringFormat("%s/%s", newRequest.c_str(), reqParts.at(n).c_str());
			}
			XMLNode subapiNode = parseRequest(newRequest.c_str(), paramSet, restReq, signature);
			if (!subapiNode.isEmpty())
				return subapiNode;
			else {
				signature = oldSignature;
				restReq->clearErrorState();
			}
		}

		if (level == reqParts.size() - 1)
			break;

		if (node.nChildNode("id") > 0) {
			if (!(subNode=node.getChildNode("id")).isEmpty()) {
				if (!(type = subNode.getAttribute("type"))) {
					restReq->addToErrorString(utils::StringFormat("API description error: ID type not provided").c_str());
					restReq->setStatus(RESTRequest::FAILED_DESCRIPTION);
					return emptyNode;
				}
				else if (strcmp(type, "integer") == 0) {
					// check if next part is integer
					if (!utils::IsTextNumeric(reqParts.at(level+1).c_str())) {
						restReq->addToErrorString(utils::StringFormat("API ID %s provided is not numeric", subNode.getAttribute("name")).c_str());
						restReq->setStatus(RESTRequest::FAILED_FORMAT);
						return emptyNode;
					}
					level++;
					node = subNode;
					continue;
				}
				else if (strcmp(type, "text") == 0) {
					// check if next part is text
					if (!reqParts.at(level+1).length()) {
						restReq->addToErrorString(utils::StringFormat("API ID %s provided is empty", subNode.getAttribute("name")).c_str());
						restReq->setStatus(RESTRequest::FAILED_FORMAT);
						return emptyNode;
					}
					level++;
					node = subNode;
					continue;
				}
				else {
					restReq->addToErrorString(utils::StringFormat("API description error: Type %s not supported", type).c_str());
					restReq->setStatus(RESTRequest::FAILED_DESCRIPTION);
					return emptyNode;
				}
			}
			else {
					restReq->addToErrorString(utils::StringFormat("API description error: No ID node found").c_str());
				restReq->setStatus(RESTRequest::FAILED_DESCRIPTION);
				return emptyNode;
			}
		}
		else if ( (subNodeCount = node.nChildNode("api")) > 0) {
			// There might be multiple api child nodes, we need to find the one with name = reqParts.at(level)
			for (i = 0; i < subNodeCount; i++) {
				if (!(subNode=node.getChildNode("api", i)).isEmpty() && (subName = subNode.getAttribute("name")) && (stricmp(reqParts.at(level+1).c_str(), subName) == 0)) {
					level++;
					node = subNode;
					break;
				}
			}
			if (i == subNodeCount) {
				// If we haven't found a matching subnode
				restReq->addToErrorString(utils::StringFormat("API description error: No API node found").c_str());
				restReq->setStatus(RESTRequest::FAILED_DESCRIPTION);
				return emptyNode;
			}
			else {
				// We did find a matching subnode
				continue;
			}
		}
		else {
			restReq->addToErrorString(utils::StringFormat("API error: No API found for signature %s_%s", signature.c_str(), reqParts.at(level+1).c_str()).c_str());
			restReq->setStatus(RESTRequest::FAILED_UNKNOWN_REQUEST);
			return emptyNode;
		}
	}
	return node;
}









bool RESTParser::extractParameters(HTTPRequest* req, std::map<std::string, RESTParam>& paramSet, RESTRequest* restReq) {
	// First find out whether the parameters are stored inside the HTTPRequest structure or as JSON/XML content
	uint32 size;
	const char* postType = req->getPostDataType("POST");
	if (!postType) {
		postType = req->getHeaderEntry("Content-Type");
	}
	const char* val, *data, *json = NULL, *xml = NULL;

	jsmn_parser parser;
	jsmntok_t tokens[256];
	int tokenCount = 0;
	int valSize;
	XMLResults xmlResults;
	XMLNode paramNode, node;

	//if (utils::stristr(req->getHeaderEntry("Content-Type"), "multipart/form-data")) {
	//	// parse all non-POST entries by name
	// #############################

	//}

	// POST entry contains JSON
	if (utils::stristr(postType, "json")) {
		if (!(json = req->getPostData("POST", size)) || !size) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No JSON POST found").c_str());
			return false;
		}
		jsmn_init(&parser);
		if ( (tokenCount = jsmn_parse(&parser, json, size, tokens, 256)) <= 0) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing JSON").c_str());
			return false;
		}
	}
	// JSON entry contains JSON
	else if ((json = req->getPostData("JSON", size)) && size) {
		jsmn_init(&parser);
		if ( (tokenCount = jsmn_parse(&parser, json, size, tokens, 256)) <= 0) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing JSON").c_str());
			return false;
		}
	}
	// POST entry contains XML
	else if (utils::stristr(postType, "xml")) {
		if (!(xml = req->getPostData("POST", size)) || !size) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No XML POST found").c_str());
			return false;
		}
		paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
		if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing XML").c_str());
			return false;
		}
	}
	// XML entry contains XML
	else if ((xml = req->getPostData("XML", size)) && size) {
		paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
		if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing XML").c_str());
			return false;
		}
	}
	// POST entry contains something and content type doesn't specify
	else if ((val = req->getPostData("POST", size)) && size) {
		// first try to see if it is JSON
		jsmn_init(&parser);
		if ( (tokenCount = jsmn_parse(&parser, val, size, tokens, 256)) > 0) {
			// great, we found valid JSON
			json = val;
		}
		// and if not then try XML
		else {
			paramNode=XMLNode::parseString(val, NULL, &xmlResults);
			if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
				restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Cannot find either JSON or XML content").c_str());
				return false;
			}
			else {
				// great, we found valid XML
				xml = val;
			}
		}
	}

	uint32 errorCount = 0;
	std::string value;
	std::map<std::string, RESTParam>::iterator i = paramSet.begin(), e = paramSet.end();
	while (i != e) {
		value.clear();
		if (json) {
			if (!(val = GetJSONValue(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED, valSize))) {
				if (!i->second.optional) {
					restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s not found in JSON", i->second.name.c_str()).c_str());
					errorCount++;
					i++;
					continue;
				}
			}
			else if (!valSize && !i->second.allowEmpty) {
				restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s empty in JSON, not allowed", i->second.name.c_str()).c_str());
				// Continue anyway, but log the error
				value.clear();
				errorCount++;
			}
			else
				value = std::string(val, valSize);
		}
		else if (xml) {
			// #############################
			return false;
			//node = paramNode.
			//if (!(val = extractXMLValue(req->getPostData("POST", size), i->second.name.c_str(), size)) || !size)
			//	return false;
		}
		else if (utils::stristr(postType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
			if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
				if (!(data = req->getPostData(i->second.name.c_str(), size)) || !size) {
					restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Binary parameter %s not found in form", i->second.name.c_str()).c_str());
					errorCount++;
					i++;
					continue;
				}
				// and now get the content-type
				if (!(val = req->getPostDataType(i->second.name.c_str()))) {
					restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
					errorCount++;
					i++;
					continue;
				}
				restReq->setBinary(i->second.name.c_str(), data, size, val);
				i++;
				continue;
			}
			else {
				if (!(val = req->getPostData(i->second.name.c_str(), size)) || !size) {
					if (!i->second.optional && (i->second.type != RESTParam::ARRAY)) {
						restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s not found in form", i->second.name.c_str()).c_str());
						errorCount++;
						i++;
						continue;
					}
					else if (i->second.type != RESTParam::ARRAY) {
						i++;
						continue;
					}
				}
				else
					value = val;
			}
		}
		else {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No form, JSON or XML found in request", i->second.name.c_str()).c_str());
			return false;
		}
		uint32 num;
		if (i->second.type == RESTParam::ARRAY) {
			std::map<std::string, RESTParam> arrayParamSet = paramSets[i->second.objects];
			if (!arrayParamSet.size()) {
				restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Array objects %s unknown", i->second.objects.c_str()).c_str());
				return false;
			}
			if (json) {
				// call with sub node 1, 2, ...
				num = 1;
				std::vector<int> jsonArrayIdx = GetJSONArrayIndexes(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED);
				std::vector<int>::iterator ia = jsonArrayIdx.begin(), ea = jsonArrayIdx.end();
				while (ia != ea) {
					value = std::string(json+tokens[*ia].start, tokens[*ia].end - tokens[*ia].start);
					// call array function...
					extractArrayParameters(req, arrayParamSet, restReq, num, value.c_str(), (uint32)value.length(), "json");
					num++;
					ia++;
				}
			}
			else if (xml) {
				// call with subnode 1, 2, ...
				return false;
			}
			else if (utils::stristr(postType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
				num = 1;
				while (extractArrayParameters(req, arrayParamSet, restReq, num, NULL, 0, NULL))
					num++;
				if (num > 1)
					restReq->clearErrorState();
				// call self with param?
				// setArrayValuesPOST();
			}
		}
		else if (value.length()) {
			if (!setParameterValue(req, i->second, value, restReq))
				return false;
		}
		i++;
	}
	return (errorCount == 0);
}






bool RESTParser::extractParameters(DataMessage* msg, std::map<std::string, RESTParam>& paramSet, RESTRequest* restReq) {
	// First find out whether the parameters are stored inside the HTTPRequest structure or as JSON/XML content
	uint32 size, size2;

	const char* postType = msg->getString("POST_CONTENT_TYPE_");
	const char* val, *data, *json = NULL, *xml = NULL;

	jsmn_parser parser;
	jsmntok_t tokens[256];
	int tokenCount = 0;
	int valSize;
	XMLResults xmlResults;
	XMLNode paramNode, node;

	// POST entry contains JSON
	if (utils::stristr(postType, "json")) {
		if (!(json = msg->getString("POST", size)) || !size) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No JSON POST found").c_str());
			return false;
		}
		jsmn_init(&parser);
		if ( (tokenCount = jsmn_parse(&parser, json, size, tokens, 256)) <= 0) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing JSON").c_str());
			return false;
		}
	}
	// JSON entry contains JSON
	else if ((json = msg->getString("JSON", size)) && size) {
		jsmn_init(&parser);
		if ( (tokenCount = jsmn_parse(&parser, json, size, tokens, 256)) <= 0) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing JSON").c_str());
			return false;
		}
	}
	// POST entry contains XML
	else if (utils::stristr(postType, "xml")) {
		if (!(xml = msg->getString("POST", size)) || !size) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No XML POST found").c_str());
			return false;
		}
		paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
		if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing XML").c_str());
			return false;
		}
	}
	// XML entry contains XML
	else if ((xml = msg->getString("XML", size)) && size) {
		paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
		if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Error parsing XML").c_str());
			return false;
		}
	}
	// POST entry contains something and content type doesn't specify
	else if ((val = msg->getString("POST", size)) && size) {
		// first try to see if it is JSON
		jsmn_init(&parser);
		if ( (tokenCount = jsmn_parse(&parser, val, size, tokens, 256)) > 0) {
			// great, we found valid JSON
			json = val;
		}
		// and if not then try XML
		else {
			paramNode=XMLNode::parseString(val, NULL, &xmlResults);
			if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
				restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Cannot find either JSON or XML content").c_str());
				return false;
			}
			else {
				// great, we found valid XML
				xml = val;
			}
		}
	}

	uint32 errorCount = 0;
	std::string value;
	std::map<std::string, RESTParam>::iterator i = paramSet.begin(), e = paramSet.end();
	while (i != e) {
		value.clear();
		if (json) {
			if (!(val = GetJSONValue(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED, valSize))) {
				if (!i->second.optional) {
					restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s not found in JSON", i->second.name.c_str()).c_str());
					errorCount++;
					i++;
					continue;
				}
			}
			else if (!valSize && !i->second.allowEmpty) {
				restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s empty in JSON, not allowed", i->second.name.c_str()).c_str());
				// Continue anyway, but log the error
				value.clear();
				errorCount++;
			}
			else
				value = std::string(val, valSize);
		}
		else if (xml) {
			// #############################
			return false;
			//node = paramNode.
			//if (!(val = extractXMLValue(req->getPostData("POST", size), i->second.name.c_str(), size)) || !size)
			//	return false;
		}
		else if (!postType || utils::stristr(postType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
			if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
				if (!(data = msg->getData(i->second.name.c_str(), size)) || !size) {
					restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Binary parameter %s not found in form", i->second.name.c_str()).c_str());
					errorCount++;
					i++;
					continue;
				}
				// and now get the content-type
				if (!(val = msg->getString((i->second.name + "_CONTENT_TYPE_").c_str(), size2)) || !size2) {
					restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
					errorCount++;
					i++;
					continue;
				}
				restReq->setBinary(i->second.name.c_str(), data, size, val);
				i++;
				continue;
			}
			else {
				if (!(val = msg->getString(i->second.name.c_str(), size)) || !size) {
					if (!i->second.optional && (i->second.type != RESTParam::ARRAY)) {
						restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Parameter %s not found in form", i->second.name.c_str()).c_str());
						errorCount++;
						i++;
						continue;
					}
					else if (i->second.type != RESTParam::ARRAY) {
						i++;
						continue;
					}
				}
				else
					value = val;
			}
		}
		else {
			restReq->addToErrorString(utils::StringFormat("Parameter extraction error: No form, JSON or XML found in request", i->second.name.c_str()).c_str());
			return false;
		}

		uint32 num;
		if (i->second.type == RESTParam::ARRAY) {
			std::map<std::string, RESTParam> arrayParamSet = paramSets[i->second.objects];
			if (!arrayParamSet.size()) {
				restReq->addToErrorString(utils::StringFormat("Parameter extraction error: Array objects %s unknown", i->second.objects.c_str()).c_str());
				return false;
			}
			if (json) {
				// call with sub node 1, 2, ...
				num = 1;
				std::vector<int> jsonArrayIdx = GetJSONArrayIndexes(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED);
				std::vector<int>::iterator ia = jsonArrayIdx.begin(), ea = jsonArrayIdx.end();
				while (ia != ea) {
					value = std::string(json+tokens[*ia].start, tokens[*ia].end - tokens[*ia].start);
					// call array function...
					extractArrayParameters(msg, arrayParamSet, restReq, num, value.c_str(), (uint32)value.length(), "json");
					num++;
					ia++;
				}
			}
			else if (xml) {
				// call with subnode 1, 2, ...
				return false;
			}
			else if (!postType || utils::stristr(postType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
				num = 1;
				while (extractArrayParameters(msg, arrayParamSet, restReq, num, NULL, 0, NULL))
					num++;
				if (num > 1)
					restReq->clearErrorState();
				// call self with param?
				// setArrayValuesPOST();
			}
		}
		else if (value.length()) {
			if (!setParameterValue(msg, i->second, value, restReq))
				return false;
		}
		else if (val) {
			if (!setParameterValue(msg, i->second, "", restReq))
				return false;
		}
		i++;
	}
	return (errorCount == 0);
}







bool RESTParser::extractArrayParameters(HTTPRequest* req, std::map<std::string, RESTParam>& paramSet, RESTRequest* restReq, int entryNum, const char* content, uint32 contentSize, const char* contentType) {
	// Called from extractParameters if any parameter is of type ARRAY
	// content can either be JSON or XML or NULL if parameters are posted as PARAM-1, PARAM-2, etc.
	// paramSet is the set inside the ARRAY

	uint32 size;
	const char* val, *data, *json = NULL, *xml = NULL;

	jsmn_parser parser;
	jsmntok_t tokens[256];
	int tokenCount = 0;
	int valSize;
	XMLResults xmlResults;
	XMLNode paramNode, node;

	if (utils::stristr(contentType, "json")) {
		if (!(json = content) || !contentSize) {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No JSON found").c_str());
			return false;
		}
		jsmn_init(&parser);
		if ( (tokenCount = jsmn_parse(&parser, json, contentSize, tokens, 256)) <= 0) {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Error parsing JSON").c_str());
			return false;
		}
	}
	else if (utils::stristr(contentType, "xml")) {
		if (!(xml = content) || !contentSize) {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No XML found").c_str());
			return false;
		}
		paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
		if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Error parsing XML").c_str());
			return false;
		}
	}

	std::string paddedParamName;
	std::string value;
	std::map<std::string, RESTParam>::iterator i = paramSet.begin(), e = paramSet.end();
	while (i != e) {
		paddedParamName = utils::StringFormat("%s_%u", i->second.name.c_str(), entryNum);
		value.clear();
		if (json) {
			// First look for the raw parameter name
			if (!(val = GetJSONValue(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED, valSize))) {
				// then look for the padded PARAM_N name
				if (!(val = GetJSONValue(tokens, tokenCount, json, paddedParamName.c_str(), JSMN_UNDEFINED, valSize))) {
					if (!i->second.optional) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in JSON", i->second.name.c_str()).c_str());
						return false;
					}
				}
			}
			else {
				if (!valSize && !i->second.allowEmpty) {
					restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s empty in JSON, not allowed", i->second.name.c_str()).c_str());
					return false;
				}
				value = std::string(val, valSize);
				// if this is a binary reference, val has the name of the binary attachment
				if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
					data = req->getPostData(value.c_str(), size);
					if (!data || !size) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s (from %s) not found in form", value.c_str(), paddedParamName.c_str()).c_str());
						return false;
					}
					// and now get the content-type
					if (!(val = req->getPostDataType(i->second.name.c_str()))) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
						return false;
					}
					restReq->setBinary(paddedParamName.c_str(), data, size, val);
					i++;
					continue;
				}
				// else leave value as text
			}
		}
		else if (xml) {
			// #############################
			return false;
			//node = paramNode.
			//if (!(val = extractXMLValue(req->getPostData("POST", size), i->second.name.c_str(), size)) || !size)
			//	return false;
		}
		else if (utils::stristr(contentType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
			if (!(data = req->getPostData(paddedParamName.c_str(), size)) || !size) {
				if (!i->second.optional) {
					restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in form", paddedParamName.c_str()).c_str());
					return false;
				}
			}
			else {
				// if this is a binary reference, val has the name of the binary attachment
				if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
					// and now get the content-type
					if (!(val = req->getPostDataType(i->second.name.c_str()))) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
						return false;
					}
					// for this don't go into the string-based setParameterValue
					restReq->setBinary(paddedParamName.c_str(), data, size, val);
					i++;
					continue;
				}
				// else leave value as text
				else
					value = data;
			}
		}
		else {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No form, JSON or XML found in request", i->second.name.c_str()).c_str());
			return false;
		}

		if (i->second.type == RESTParam::ARRAY) {
			// For now we do not support nested arrays
		}
		else if (value.length()) {
			if (!setParameterValue(req, i->second, value, restReq, entryNum))
				return false;
		}
		i++;
	}
	return true;
}



bool RESTParser::extractArrayParameters(DataMessage* msg, std::map<std::string, RESTParam>& paramSet, RESTRequest* restReq, int entryNum, const char* content, uint32 contentSize, const char* contentType) {
	// Called from extractParameters if any parameter is of type ARRAY
	// content can either be JSON or XML or NULL if parameters are posted as PARAM-1, PARAM-2, etc.
	// paramSet is the set inside the ARRAY

	uint32 size, size2;
	const char* val, *data, *json = NULL, *xml = NULL;

	jsmn_parser parser;
	jsmntok_t tokens[256];
	int tokenCount = 0;
	int valSize;
	XMLResults xmlResults;
	XMLNode paramNode, node;

	if (utils::stristr(contentType, "json")) {
		if (!(json = content) || !contentSize) {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No JSON found").c_str());
			return false;
		}
		jsmn_init(&parser);
		if ( (tokenCount = jsmn_parse(&parser, json, contentSize, tokens, 256)) <= 0) {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Error parsing JSON").c_str());
			return false;
		}
	}
	else if (utils::stristr(contentType, "xml")) {
		if (!(xml = content) || !contentSize) {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No XML found").c_str());
			return false;
		}
		paramNode=XMLNode::parseString(xml, NULL, &xmlResults);
		if ((xmlResults.error != eXMLErrorNone) || (paramNode.isEmpty())) {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Error parsing XML").c_str());
			return false;
		}
	}

	std::string paddedParamName;
	std::string value;
	std::map<std::string, RESTParam>::iterator i = paramSet.begin(), e = paramSet.end();
	while (i != e) {
		paddedParamName = utils::StringFormat("%s_%u", i->second.name.c_str(), entryNum);
		value.clear();
		if (json) {
			// First look for the raw parameter name
			if (!(val = GetJSONValue(tokens, tokenCount, json, i->second.name.c_str(), JSMN_UNDEFINED, valSize))) {
				// then look for the padded PARAM_N name
				if (!(val = GetJSONValue(tokens, tokenCount, json, paddedParamName.c_str(), JSMN_UNDEFINED, valSize))) {
					if (!i->second.optional) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in JSON", i->second.name.c_str()).c_str());
						return false;
					}
				}
			}
			else {
				if (!valSize && !i->second.allowEmpty) {
					restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s empty in JSON, not allowed", i->second.name.c_str()).c_str());
					return false;
				}
				value = std::string(val, valSize);
				// if this is a binary reference, val has the name of the binary attachment
				if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
					data = msg->getData(value.c_str(), size);
					if (!data || !size) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s (from %s) not found in form", value.c_str(), paddedParamName.c_str()).c_str());
						return false;
					}
					// and now get the content-type
					if (!(val = msg->getString((value + "_CONTENT_TYPE_").c_str(), size2)) || !size2) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
						return false;
					}
					restReq->setBinary(paddedParamName.c_str(), data, size, val);
					i++;
					continue;
				}
				// else leave value as text
			}
		}
		else if (xml) {
			// #############################
			return false;
			//node = paramNode.
			//if (!(val = extractXMLValue(req->getPostData("POST", size), i->second.name.c_str(), size)) || !size)
			//	return false;
		}
		else if (!contentType || utils::stristr(contentType, "form")) { // both multipart/form-data and application/x-www-form-urlencoded
			if (i->second.type == RESTParam::IMAGE || i->second.type == RESTParam::VIDEO || i->second.type == RESTParam::BINARY) {
				if (!(data = msg->getData(paddedParamName.c_str(), size)) || !size) {
					if (!i->second.optional) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in form", paddedParamName.c_str()).c_str());
						return false;
					}
					else {
						i++;
						continue;
					}
				}
				// and now get the content-type
				if (!(val = msg->getString((paddedParamName + "_CONTENT_TYPE_").c_str(), size2)) || !size2) {
					restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Binary parameter %s content type not found in form", i->second.name.c_str()).c_str());
					return false;
				}
				restReq->setBinary(paddedParamName.c_str(), data, size, val);
				i++;
				continue;
			}
			else {
				if (!(val = msg->getString(paddedParamName.c_str(), size)) || !size) {
					if (!i->second.optional) {
						restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: Parameter %s not found in form", i->second.name.c_str()).c_str());
						return false;
					}
					else {
						i++;
						continue;
					}
				}
				value = val;
			}
		}
		else {
			restReq->addToErrorString(utils::StringFormat("Array parameter extraction error: No form, JSON or XML found in request", i->second.name.c_str()).c_str());
			return false;
		}

		if (i->second.type == RESTParam::ARRAY) {
			// For now we do not support nested arrays
		}
		else if (value.length()) {
			if (!setParameterValue(msg, i->second, value, restReq, entryNum))
				return false;
		}
		i++;
	}
	return true;
}

































bool RESTParser::setParameterValue(HTTPRequest* req, RESTParam &param, std::string value, RESTRequest* restReq, uint32 entryNum) {

	// if entryName = 0 this is a normal parameter, if not, it is an array parameter

	uint32 size;
	const char* data, *val;
	utils::StringSingleReplace(value, "\"", "", false);

	std::string paddedParamName;
	if (entryNum)
		paddedParamName = utils::StringFormat("%s_%u", param.name.c_str(), entryNum);
	else
		paddedParamName = param.name;

	switch(param.type) {
		case RESTParam::INTEGER:
			if (!utils::IsTextNumeric(value.c_str())) {
				restReq->addToErrorString(utils::StringFormat("Parameter error: %s = %s is not numeric", paddedParamName.c_str(), value.c_str()).c_str());
				return false;
			}
			restReq->setInt(paddedParamName.c_str(), utils::Ascii2Int64(value.c_str()));
			break;
		case RESTParam::FLOAT:
			if (!utils::IsTextNumeric(value.c_str())) {
				restReq->addToErrorString(utils::StringFormat("Parameter error: %s = %s is not numeric", paddedParamName.c_str(), value.c_str()).c_str());
				return false;
			}
			restReq->setDouble(paddedParamName.c_str(), (double)utils::Ascii2Float64(value.c_str()));
			break;
		case RESTParam::TEXT:
			restReq->setString(paddedParamName.c_str(), value.c_str());
			break;
		case RESTParam::IMAGE:
		case RESTParam::VIDEO:
		case RESTParam::BINARY:
			// Check that post contains an entry of value
			if (!(data = req->getPostData(value.c_str(), size))) {
				restReq->addToErrorString(utils::StringFormat("Parameter error: cannot find post entry called %s for parameter %s", value.c_str(), paddedParamName.c_str()).c_str());
				return false;
			}
			if (!(val = req->getPostDataType(value.c_str()))) {
				restReq->addToErrorString(utils::StringFormat("Parameter error: cannot find post type for entry called %s for parameter %s", value.c_str(), paddedParamName.c_str()).c_str());
				return false;
			}
			restReq->setBinary(paddedParamName.c_str(), data, size, val);
			break;
		//case RESTParam::ARRAY:
			// ###################
			// break;
			//return false;
		default:
			restReq->addToErrorString(utils::StringFormat("Parameter error: %s type is not supported", paddedParamName.c_str(), value.c_str()).c_str());
			return false;
	}
	return true;
}





bool RESTParser::setParameterValue(DataMessage* msg, RESTParam &param, std::string value, RESTRequest* restReq, uint32 entryNum) {

	// if entryName = 0 this is a normal parameter, if not, it is an array parameter

	uint32 size, size2;
	const char* data, *val;
	utils::StringSingleReplace(value, "\"", "", false);

	std::string paddedParamName;
	if (entryNum)
		paddedParamName = utils::StringFormat("%s_%u", param.name.c_str(), entryNum);
	else
		paddedParamName = param.name;

	switch(param.type) {
		case RESTParam::INTEGER:
			if (!utils::IsTextNumeric(value.c_str())) {
				restReq->addToErrorString(utils::StringFormat("Parameter error: %s = %s is not numeric", paddedParamName.c_str(), value.c_str()).c_str());
				return false;
			}
			restReq->setInt(paddedParamName.c_str(), utils::Ascii2Int64(value.c_str()));
			break;
		case RESTParam::FLOAT:
			if (!utils::IsTextNumeric(value.c_str())) {
				restReq->addToErrorString(utils::StringFormat("Parameter error: %s = %s is not numeric", paddedParamName.c_str(), value.c_str()).c_str());
				return false;
			}
			restReq->setDouble(paddedParamName.c_str(), (double)utils::Ascii2Float64(value.c_str()));
			break;
		case RESTParam::TEXT:
			restReq->setString(paddedParamName.c_str(), value.c_str());
			break;
		case RESTParam::IMAGE:
		case RESTParam::VIDEO:
		case RESTParam::BINARY:
			// Check that post contains an entry of value
			if (!(data = msg->getData(value.c_str(), size))) {
				restReq->addToErrorString(utils::StringFormat("Parameter error: cannot find post entry called %s for parameter %s", value.c_str(), paddedParamName.c_str()).c_str());
				return false;
			}
			// and now get the content-type
			if (!(val = msg->getString((value + "_CONTENT_TYPE_").c_str(), size2)) || !size2) {
				restReq->addToErrorString(utils::StringFormat("Parameter error: cannot find post type for entry called %s for parameter %s", value.c_str(), paddedParamName.c_str()).c_str());
				return false;
			}
			restReq->setBinary(paddedParamName.c_str(), data, size, val);
			break;
		//case RESTParam::ARRAY:
			// ###################
			// break;
			//return false;
		default:
			restReq->addToErrorString(utils::StringFormat("Parameter error: %s type is not supported", paddedParamName.c_str(), value.c_str()).c_str());
			return false;
	}
	return true;
}




















XMLNode RESTParser::findTopNodeForRequest(const char* topNodeString) {
	const char* name;
	XMLNode node;
	uint32 i, count = mainNode.nChildNode("api");
	for(i=0; i<count; i++) {
		if (!(node = mainNode.getChildNode("api", i)).isEmpty()) {
			name = node.getAttribute("name");
			if (strcmp(topNodeString, name) == 0)
				return node;
		}
	}
	return XMLNode::emptyXMLNode;
}














RESTRequest::RESTRequest(DataMessage* msg) {
	bool ok;
	createTime = msg->getTime("BUILTIN_CREATETIME", ok);
	status = (Status)msg->getInt("BUILTIN_STATUS", ok);
	signature = getString("BUILTIN_REQUEST_SIGNATURE", ok);
	requireAuthentication = (msg->getInt("BUILTIN_REQUIRE_AUTHENTICATION", ok) != 0);
	std::map<std::string, std::string> filterMapTemp = utils::TextMapSplit(msg->getString("BUILTIN_FILTERMAP"), ";;;", "=");
	std::map<std::string, std::string>::iterator i = filterMapTemp.begin(), e = filterMapTemp.end();
	while (i != e) {
		filterMap[html::DecodeHTML(i->first)] = html::DecodeHTML(i->second);
		i++;
	}
	storage = msg;
	httpCall = NULL;
}

RESTRequest::~RESTRequest() {
	delete(storage);
	// we don't own httpCall, just remove link
	httpCall = NULL;
}


DataMessage* RESTRequest::getStorageMessageCopy() {
	DataMessage* msg = new DataMessage(*storage);
	msg->setTime("BUILTIN_CREATETIME", createTime);
	msg->setInt("BUILTIN_STATUS", status);
	if (requireAuthentication)
		msg->setInt("BUILTIN_REQUIRE_AUTHENTICATION", 1);
	std::string filterString;
	std::multimap<std::string, std::string>::iterator i = filterMap.begin(), e = filterMap.end();
	while (i != e) {
		if (filterString.length())
			filterString = utils::StringFormat("%s;;;%s=%s", filterString.c_str(), html::EncodeHTML(i->first).c_str(), html::EncodeHTML(i->second).c_str());
		else
			filterString = utils::StringFormat("%s=%s", html::EncodeHTML(i->first).c_str(), html::EncodeHTML(i->second).c_str());
		i++;
	}
	msg->setString("BUILTIN_FILTERMAP", filterString.c_str());
	msg->setString("BUILTIN_REQUEST_SIGNATURE", signature.c_str());
	return msg;
}


HTTPRequest* RESTRequest::generateHTTPCallRequest() {
	if (!httpCall)
		return NULL;
	return httpCall->generateHTTPRequest(this);
}

std::string RESTRequest::getHTTPCallURL() {
	if (!httpCall)
		return "";
	return httpCall->url;
}





bool RESTParser::UnitTest() {

	// A self-contained REST API definition exercising paramsets, an http call,
	// a top-level api with an integer id child and a nested sub-api.
	const char* restDesc =
		"<restapi>"
		"  <api name=\"users\" ops=\"get,post\">"
		"    <id name=\"userid\" type=\"integer\" ops=\"get,put,delete\" />"
		"    <api name=\"profile\" ops=\"get\" />"
		"  </api>"
		"</restapi>";

	// 1. init with a valid definition
	unittest::progress(10, "init valid definition");
	RESTParser parser;
	if (!parser.init(restDesc)) {
		unittest::fail("RESTParser test: init on valid definition failed: %s", parser.getLastErrorString());
		return false;
	}
	unittest::detail("init succeeded, lastError='%s'", parser.getLastErrorString());

	// 2. init with malformed definitions must fail and report an error
	unittest::progress(25, "init invalid definitions");
	{
		RESTParser bad;
		if (bad.init("<notrestapi></notrestapi>")) {
			unittest::fail("RESTParser test: init accepted definition without <restapi> tag");
			return false;
		}
		if (!strlen(bad.getLastErrorString())) {
			unittest::fail("RESTParser test: missing-tag init did not set an error string");
			return false;
		}
		unittest::detail("missing-tag init error: %s", bad.getLastErrorString());

		RESTParser malformed;
		if (malformed.init("<restapi><api name=")) {
			unittest::fail("RESTParser test: init accepted malformed XML");
			return false;
		}
	}

	// 3. parse a valid GET request via an HTTPRequest
	unittest::progress(45, "parse GET request");
	{
		bool isInvalid = false;
		const char* raw =
			"GET /users HTTP/1.1\r\n"
			"Host: localhost\r\n"
			"\r\n";
		HTTPRequest httpReq;
		if (!httpReq.processHeader(raw, (uint32)strlen(raw), isInvalid) || isInvalid) {
			unittest::fail("RESTParser test: processHeader on GET failed (isInvalid=%d)", (int)isInvalid);
			return false;
		}
		if (httpReq.type != HTTP_GET) {
			unittest::fail("RESTParser test: parsed request type != HTTP_GET (got %u)", (unsigned)httpReq.type);
			return false;
		}
		RESTRequest* restReq = parser.parseRequest(&httpReq);
		if (!restReq) {
			unittest::fail("RESTParser test: parseRequest(HTTPRequest) returned NULL");
			return false;
		}
		if (restReq->getStatus() != RESTRequest::SUCCESS) {
			unittest::fail("RESTParser test: GET /users status %d != SUCCESS (err='%s')",
				(int)restReq->getStatus(), restReq->getErrorString());
			delete(restReq);
			return false;
		}
		unittest::detail("GET /users parsed, status=%d", (int)restReq->getStatus());
		delete(restReq);
	}

	// 4. parse a request for an unknown API: must not crash and must report failure
	unittest::progress(65, "parse unknown request");
	{
		bool isInvalid = false;
		const char* raw =
			"GET /doesnotexist HTTP/1.1\r\n"
			"Host: localhost\r\n"
			"\r\n";
		HTTPRequest httpReq;
		if (!httpReq.processHeader(raw, (uint32)strlen(raw), isInvalid) || isInvalid) {
			unittest::fail("RESTParser test: processHeader on unknown GET failed");
			return false;
		}
		RESTRequest* restReq = parser.parseRequest(&httpReq);
		if (!restReq) {
			unittest::fail("RESTParser test: parseRequest(unknown) returned NULL");
			return false;
		}
		if (restReq->getStatus() == RESTRequest::SUCCESS) {
			unittest::fail("RESTParser test: unknown request unexpectedly reported SUCCESS");
			delete(restReq);
			return false;
		}
		unittest::detail("unknown request status=%d err='%s'",
			(int)restReq->getStatus(), restReq->getErrorString());
		delete(restReq);
	}

	// 5. parse a request expressed as a DataMessage (the message-based entry point)
	unittest::progress(80, "parse request from message");
	{
		DataMessage msg(CTRL_TEST, 0);
		msg.setString("URI", "/users/42");
		msg.setInt("HTTP_OPERATION", HTTP_GET);
		RESTRequest* restReq = parser.parseRequest(&msg);
		if (!restReq) {
			unittest::fail("RESTParser test: parseRequest(DataMessage) returned NULL");
			return false;
		}
		// Either SUCCESS (id matched) or a defined failure status; just must not crash.
		unittest::detail("message request /users/42 status=%d", (int)restReq->getStatus());
		delete(restReq);
	}

	// 6. throughput: repeatedly init + parse to produce a stable metric
	unittest::progress(90, "throughput measurement");
	{
		const int N = 2000;
		const char* raw =
			"GET /users HTTP/1.1\r\n"
			"Host: localhost\r\n"
			"\r\n";
		uint32 rawLen = (uint32)strlen(raw);
		uint64 t0 = GetTimeNow();
		for (int n = 0; n < N; n++) {
			bool isInvalid = false;
			HTTPRequest httpReq;
			if (!httpReq.processHeader(raw, rawLen, isInvalid)) {
				unittest::fail("RESTParser test: processHeader failed during throughput loop at %d", n);
				return false;
			}
			RESTRequest* restReq = parser.parseRequest(&httpReq);
			if (!restReq) {
				unittest::fail("RESTParser test: parseRequest returned NULL during throughput loop at %d", n);
				return false;
			}
			delete(restReq);
		}
		double us = (double)(GetTimeNow() - t0);
		if (us > 0) {
			unittest::metric("parse_throughput", (double)N / us * 1e6, "req/s", true);
			unittest::metric("avg_parse_latency", us / N, "us", false);
		}
	}

	unittest::progress(100, "done");
	return true;
}

void Register_RESTParser_Tests() {
	UnitTestRunner::instance().registerTest("rest", RESTParser::UnitTest,
		"REST API definition parsing, request matching and parameter extraction", "network");
}
















RESTHTTPCall::RESTHTTPCall() {
}

RESTHTTPCall::~RESTHTTPCall() {
}

bool RESTHTTPCall::init(XMLNode objectNode) {

	name = objectNode.getAttribute("name");
	url = objectNode.getAttribute("url");
	contentType = objectNode.getAttribute("contenttype");
	cacheControl = objectNode.getAttribute("cachecontrol");

	const char* opsString = objectNode.getAttribute("ops");

	if (!opsString || !strlen(opsString))
		ops = HTTP_GET;
	else if (stricmp(opsString, "post") == 0)
		ops = HTTP_POST;
	else if (stricmp(opsString, "put") == 0)
		ops = HTTP_PUT;
	else if (stricmp(opsString, "delete") == 0)
		ops = HTTP_DELETE;
	else
		ops = HTTP_GET;

	if (!name.length() || !url.length())
		return false;

	if (!contentType.length())
		contentType = "form-data";

	if (!cacheControl.length())
		cacheControl = "no-cache";

	const char* entryName, *entryValue, *entryParam, *entryContentType;

	XMLNode node;
	uint32 i, count = objectNode.nChildNode("header");
	for (i = 0; i < count; i++) {
		if (!(node = objectNode.getChildNode("header", i)).isEmpty()) {
			entryName = node.getAttribute("name");
			entryValue = node.getAttribute("value");
			entryParam = node.getAttribute("param");
			if (entryName && strlen(entryName)) {
				if (entryValue && strlen(entryValue)) {
					headerEntries[entryName] = entryValue;
				}
				else if (entryParam && strlen(entryParam)) {
					headerParams[entryName] = entryParam;
				}
			}
		}
	}

	count = objectNode.nChildNode("body");
	for (i = 0; i < count; i++) {
		if (!(node = objectNode.getChildNode("body", i)).isEmpty()) {
			entryName = node.getAttribute("name");
			entryValue = node.getAttribute("value");
			entryParam = node.getAttribute("param");
			entryContentType = node.getAttribute("contenttype");
			if (entryName && strlen(entryName)) {
				if (entryValue && strlen(entryValue)) {
					bodyEntries[entryName] = entryValue;
				}
				else if (entryParam && strlen(entryParam)) {
					bodyParams[entryName] = entryParam;
				}

				if (entryContentType && strlen(entryContentType)) {
					bodyContentTypes[entryName] = entryContentType;
				}
			}
		}
	}

	//if (!headerEntries.size() && !headerParams.size() && !bodyEntries.size() && !bodyParams.size())
	//	return false;

	return true;
}



HTTPRequest* RESTHTTPCall::generateHTTPRequest(RESTRequest* restReq) {

	HTTPRequest* req = new HTTPRequest();

	std::map<std::string, std::string> allHeaderEntries;

	// Copy all the static headers
	allHeaderEntries = headerEntries;

	bool success;
	const char* str;
	std::map<std::string, std::string>::iterator hI = headerParams.begin(), hE = headerParams.end();
	// Look for all the parameter headers
	while (hI != hE) {
		// If the param string is provided in the restReq add it as the value of a header entry
		if ((str = restReq->getString(hI->second.c_str(), success)) && success)
			allHeaderEntries[hI->first] = str;
		hI++;
	}

	std::map<std::string, HTTPPostEntry*> allBodyEntries;
	HTTPPostEntry* postEntry;

	std::map<std::string, std::string>::iterator sI, sE = bodyContentTypes.end();
	// Now iterate through the static body entries
	std::map<std::string, std::string>::iterator bI = bodyEntries.begin(), bE = bodyEntries.end();
	while (bI != bE) {
		postEntry = new HTTPPostEntry();
		postEntry->name = bI->first;
		if ((sI = bodyContentTypes.find(bI->first)) != sE)
			postEntry->type = sI->second;
		else
			postEntry->type = "text/plain";
		postEntry->contentSize = (uint32)bI->second.length();
		postEntry->content = new char[postEntry->contentSize + 1];
		memcpy(postEntry->content, bI->second.c_str(), postEntry->contentSize + 1);
		allBodyEntries[postEntry->name] = postEntry;
		bI++;
	}

	// Now iterate through the parameter body entries
	bI = bodyParams.begin(), bE = bodyParams.end();
	while (bI != bE) {
		postEntry = new HTTPPostEntry();
		postEntry->name = bI->first;
		if ((sI = bodyContentTypes.find(bI->first)) != sE)
			postEntry->type = sI->second;
		else
			postEntry->type = "text/plain";

		if (html::IsMimeBinary(postEntry->type.c_str())) {
			// Copy as binary content
			postEntry->content = restReq->getDataCopy(bI->second.c_str(), postEntry->contentSize, success);
		}
		else {
			// Copy as text content
			if ((str = restReq->getString(bI->second.c_str(), success)) && success) {
				postEntry->contentSize = (uint32)strlen(str);
				postEntry->content = new char[postEntry->contentSize + 1];
				memcpy(postEntry->content, str, postEntry->contentSize + 1);
			}
		}

		allBodyEntries[postEntry->name] = postEntry;
		bI++;
	}

	req->createMultipartRequest(ops, html::GetHostFromURL(url).c_str(), html::GetURIFromURL(url).c_str(), allHeaderEntries, allBodyEntries, true, 0);

	std::map<std::string, HTTPPostEntry*>::iterator i = allBodyEntries.begin(), e = allBodyEntries.end();
	while (i != e) {
		delete(i->second);
		i++;
	}

	return req;



	//if (!headerEntries.size() && !headerParams.size() && !bodyParams.size() && !bodyParams.size()) {
	//	req->createRequest(ops, "", url.c_str(), NULL, 0, false, 0);
	//}

	//if (content && contentSize)
	//	req->createRequest(HTTP_POST, "", uriString.c_str(), content, contentSize, false, 0);
	//else
	//	req->createRequest(HTTP_GET, "", uriString.c_str(), NULL, 0, false, 0);
	//HTTPReply* reply = makeHTTPRequest(req, hostString.c_str(), port, encryption, timeout);




}


} // namespace cmlabs

