#include <uwsgi.h>

#ifdef UWSGI_XML_LIBXML2
#include <libxml/parser.h>
#include <libxml/tree.h>
#endif

extern struct uwsgi_server uwsgi;

/*

	rpc-HTTP interface.

	modifier2 changes the parser behaviours:

	0 -> return uwsgi header + rpc response
	1 -> return raw rpc response
	2 -> split PATH_INFO to get func name and args and return as HTTP response with content_type as application/binary or  Accept request header (if different from *)
	3 -> set xmlrpc wrapper (requires libxml2)
	4 -> set jsonrpc wrapper (requires libjansson)
	5 -> used in uwsgi response to signal the response is a uwsgi dictionary followed by the body (the dictionary must contains a CONTENT_LENGTH key)

*/

#ifdef UWSGI_XML_LIBXML2
// raise error on non-string args
static int uwsgi_rpc_xmlrpc_string(xmlNode *element, char **argv, uint16_t *argvs, uint8_t *argc) {
	
	xmlNode *node;
	for (node = element->children; node; node = node->next) {
		if (node->type == XML_ELEMENT_NODE) {
			if (strcmp((char *) node->name, "string")) {
				return -1;
			}
			if (!node->children) return -1;
			if (!node->children->content) return -1;
			*argc+=1;
			argv[*argc] = (char *) node->children->content;
			argvs[*argc] = strlen(argv[*argc]);
		}
	}

	return 0;
}
static int uwsgi_rpc_xmlrpc_value(xmlNode *element, char **argv, uint16_t *argvs, uint8_t *argc) {
	xmlNode *node;
        for (node = element->children; node; node = node->next) {
                if (node->type == XML_ELEMENT_NODE) {
                        if (!strcmp((char *) node->name, "value")) {
				if (uwsgi_rpc_xmlrpc_string(node, argv, argvs, argc)) {
					return -1;
				}
			}
		}
	}
	return 0;
}

static int uwsgi_rpc_xmlrpc_args(xmlNode *element, char **argv, uint16_t *argvs, uint8_t *argc) {
	xmlNode *node;
        for (node = element->children; node; node = node->next) {
                if (node->type == XML_ELEMENT_NODE) {
                        if (!strcmp((char *) node->name, "param")) {
				if (uwsgi_rpc_xmlrpc_value(node, argv, argvs, argc)) {
					return -1;
				}
                        }
                }
        }

	return 0;
}

static int uwsgi_rpc_xmlrpc(struct wsgi_request *wsgi_req, xmlDoc *doc, char **argv, uint16_t *argvs, uint8_t *argc, char **response_buf) {
	char *method = NULL;
	xmlNode *element = xmlDocGetRootElement(doc);
        if (!element) return -1;

        if (strcmp((char *) element->name, "methodCall")) return -1;

	*argc = 0;
	xmlNode *node;
        for (node = element->children; node; node = node->next) {
                if (node->type == XML_ELEMENT_NODE) {
                	if (!strcmp((char *) node->name, "methodName") && node->children) {
				method = (char *) node->children->content;
			}
			else if (!strcmp((char *) node->name, "params")) {
				if (uwsgi_rpc_xmlrpc_args(node, argv, argvs, argc)) return -1;	
			}
                }
        }

	if (!method) return -1;
	uint64_t content_len = uwsgi_rpc(method, *argc, argv+1, argvs+1, response_buf);

	if (!*response_buf) return -1;
	// add final NULL byte
	char *tmp_buf = realloc(*response_buf, content_len + 1);
	if (!tmp_buf) return -1;
	*response_buf = tmp_buf;
	*response_buf[content_len] = 0;

	xmlDoc *rdoc = xmlNewDoc(BAD_CAST "1.0");
        xmlNode *m_response = xmlNewNode(NULL, BAD_CAST "methodResponse");
        xmlDocSetRootElement(rdoc, m_response);
	xmlNode *x_params = xmlNewChild(m_response, NULL, BAD_CAST "params", NULL);
	xmlNode *x_param = xmlNewChild(x_params, NULL, BAD_CAST "param", NULL);
	xmlNode *x_value = xmlNewChild(x_param, NULL, BAD_CAST "value", NULL);
        xmlNewTextChild(x_value, NULL, BAD_CAST "string", BAD_CAST response_buf);
	
	xmlChar *xmlbuf;
        int xlen = 0;
        xmlDocDumpFormatMemory(rdoc, &xmlbuf, &xlen, 1);
	if (uwsgi_response_prepare_headers(wsgi_req,"200 OK", 6)) {
		xmlFreeDoc(rdoc);
		xmlFree(xmlbuf);
		return -1;
	}
        uwsgi_response_add_content_length(wsgi_req, xlen);
        uwsgi_response_add_content_type(wsgi_req, "application/xml", 15);
        uwsgi_response_write_body_do(wsgi_req, (char *) xmlbuf, xlen);

	xmlFreeDoc(rdoc);
	xmlFree(xmlbuf);

	return 0;
}
#endif

static int uwsgi_rpc_request(struct wsgi_request *wsgi_req) {

	// this is the list of args
	char *argv[UMAX8];
	// this is the size of each argument
	uint16_t argvs[UMAX8];
	// maximum number of supported arguments
	uint8_t argc = 0xff;
	// response output
	char *response_buf = NULL;
	// response size
	size_t content_len = 0;

	/* Standard RPC request */
        if (!wsgi_req->uh->pktsize) {
                uwsgi_log("Empty RPC request. skip.\n");
                return -1;
        }

	if (wsgi_req->uh->modifier2 == 2) {
		if (uwsgi_parse_vars(wsgi_req)) {
                	uwsgi_log("Invalid RPC request. skip.\n");
                	return -1;
		}

		if (wsgi_req->path_info_len == 0) {
			uwsgi_500(wsgi_req);
			return UWSGI_OK;
		}

		char *args = NULL;
		if (wsgi_req->path_info[0] == '/') {
			args = uwsgi_concat2n(wsgi_req->path_info+1, wsgi_req->path_info_len-1, "", 0);
		}
		else {
			args = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, "", 0);
		}

		argc = 0;
		char *ctx = NULL;
		argv[0] = strtok_r(args, "/", &ctx);
		if (!argv[0]) {
			free(args);
			uwsgi_500(wsgi_req);
			return UWSGI_OK;
		}
		char *p = strtok_r(NULL, "/", &ctx);
		while(p) {
			argc++;
			argv[argc] = p;
			argvs[argc] = strlen(p);
			p = strtok_r(NULL, "/", &ctx);
		}
		
		content_len = uwsgi_rpc(argv[0], argc, argv+1, argvs+1, &response_buf);
		free(args);

		if (!content_len) {
			if (response_buf) free(response_buf);
			uwsgi_404(wsgi_req);
			return UWSGI_OK;
		}
		if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) {if (response_buf) free(response_buf); return -1;}
		if (uwsgi_response_add_content_length(wsgi_req, content_len)) {if (response_buf) free(response_buf); return -1;}
		uint16_t ctype_len = 0;
		char *ctype = uwsgi_get_var(wsgi_req, "HTTP_ACCEPT", 11, &ctype_len);
		if (!ctype || !uwsgi_strncmp(ctype, ctype_len, "*/*" ,3) || !uwsgi_strncmp(ctype, ctype_len, "*", 1)) {
			if (uwsgi_response_add_content_type(wsgi_req, "application/binary", 18)) {if (response_buf) free(response_buf); return -1;}
		}
		else {
			if (uwsgi_response_add_content_type(wsgi_req, ctype, ctype_len)) {if (response_buf) free(response_buf); return -1;}
		}
		goto sendbody;
	}

#ifdef UWSGI_XML_LIBXML2
	if (wsgi_req->uh->modifier2 == 3) {
		if (wsgi_req->post_cl == 0) {
			uwsgi_500(wsgi_req);
			return UWSGI_OK;
		}
		ssize_t body_len = 0;
                char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);	
		xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
                if (!doc) {
			uwsgi_500(wsgi_req);
			return UWSGI_OK;
		}
                int ret = uwsgi_rpc_xmlrpc(wsgi_req, doc, argv, argvs, &argc, &response_buf);
                xmlFreeDoc(doc);
		if (ret) {
			uwsgi_500(wsgi_req);
		}
		if (response_buf) free(response_buf);
		return UWSGI_OK;
	}
#endif

	if (uwsgi_parse_array(wsgi_req->buffer, wsgi_req->uh->pktsize, argv, argvs, &argc)) {
                uwsgi_log("Invalid RPC request. skip.\n");
                return -1;
	}

	// call the function (output will be in wsgi_req->buffer)
	content_len = uwsgi_rpc(argv[0], argc-1, argv+1, argvs+1, &response_buf);
	if (!response_buf) return -1;

	// using modifier2 we may want a raw output
	if (wsgi_req->uh->modifier2 == 0) {
		if (content_len > 0xffff) {
			// signal dictionary
			wsgi_req->uh->modifier2 = 5;
			struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size);
			if (uwsgi_buffer_append_keynum(ub, "CONTENT_LENGTH", 14 , content_len)) {
				uwsgi_buffer_destroy(ub);
				free(response_buf);	
				return -1;	
			}
			// fix uwsgi header
			wsgi_req->uh->pktsize = ub->pos;
			if (uwsgi_response_write_body_do(wsgi_req, (char *) wsgi_req->uh, 4)) {
				uwsgi_buffer_destroy(ub);
				free(response_buf);	
				return -1;
			}
			if (uwsgi_response_write_body_do(wsgi_req, ub->buf, ub->pos)) {
				uwsgi_buffer_destroy(ub);
				free(response_buf);	
                        	return -1;
                	}
			uwsgi_buffer_destroy(ub);
		}
		else {
			wsgi_req->uh->pktsize = content_len;
			if (uwsgi_response_write_body_do(wsgi_req, (char *) wsgi_req->uh, 4)) {
				free(response_buf);	
				return -1;
			}
		}

	}


sendbody:
	// write the response
	uwsgi_response_write_body_do(wsgi_req, response_buf, content_len);
	free(response_buf);	
	return UWSGI_OK;
}

#ifdef UWSGI_ROUTING
static int uwsgi_routing_func_rpc(struct wsgi_request *wsgi_req, struct uwsgi_route *ur) {
	int ret = -1;
	// this is the list of args
        char *argv[UMAX8];
        // this is the size of each argument
        uint16_t argvs[UMAX8];
	// this is a placeholder for tmp uwsgi_buffers
	struct uwsgi_buffer *ubs[UMAX8];

	char **r_argv = (char **) ur->data2;
	uint16_t *r_argvs = (uint16_t *) ur->data3;

	char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

	uint64_t i;
	for(i=0;i<ur->custom;i++) {
		ubs[i] = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, r_argv[i], r_argvs[i]);
		if (!ubs[i]) goto end;
		argv[i] = ubs[i]->buf;
		argvs[i] = ubs[i]->pos;
	}

	// ok we now need to check it it is a local call or a remote one
	char *func = uwsgi_str(ur->data);
	char *remote = NULL;
	char *at = strchr(func, '@');
	if (at) {
		*at = 0;
		remote = at+1;
	}
	uint64_t size;
	char *response = uwsgi_do_rpc(remote, func, ur->custom, argv, argvs, &size);
	free(func);
	if (!response) goto end;

	ret = UWSGI_ROUTE_BREAK;

	if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) {free(response) ; goto end;}
        if (uwsgi_response_add_content_length(wsgi_req, size)) {free(response) ; goto end;}
	uwsgi_response_write_body_do(wsgi_req, response, size);
	free(response);

end:
	for(i=0;i<ur->custom;i++) {
		if (ubs[i] != NULL) {
			uwsgi_buffer_destroy(ubs[i]);
		}
	}
	return ret;
}

static int uwsgi_routing_func_rpc_blob(struct wsgi_request *wsgi_req, struct uwsgi_route *ur) {
        int ret = -1;
        // this is the list of args
        char *argv[UMAX8];
        // this is the size of each argument
        uint16_t argvs[UMAX8];
        // this is a placeholder for tmp uwsgi_buffers
        struct uwsgi_buffer *ubs[UMAX8];

        char **r_argv = (char **) ur->data2;
        uint16_t *r_argvs = (uint16_t *) ur->data3;

        char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

        uint64_t i;
        for(i=0;i<ur->custom;i++) {
                ubs[i] = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, r_argv[i], r_argvs[i]);
                if (!ubs[i]) goto end;
                argv[i] = ubs[i]->buf;
                argvs[i] = ubs[i]->pos;
        }

        // ok we now need to check it it is a local call or a remote one
        char *func = uwsgi_str(ur->data);
        char *remote = NULL;
        char *at = strchr(func, '@');
        if (at) {
                *at = 0;
                remote = at+1;
        }
        uint64_t size;
        char *response = uwsgi_do_rpc(remote, func, ur->custom, argv, argvs, &size);
        free(func);
        if (!response) goto end;

        ret = UWSGI_ROUTE_NEXT;

	// optimization
	if (!wsgi_req->headers_sent) {
        	if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) {free(response) ; goto end;}
        	if (uwsgi_response_add_connection_close(wsgi_req)) {free(response) ; goto end;}
	}
        uwsgi_response_write_body_do(wsgi_req, response, size);
        free(response);

end:
        for(i=0;i<ur->custom;i++) {
                if (ubs[i] != NULL) {
                        uwsgi_buffer_destroy(ubs[i]);
                }
        }
        return ret;
}

static int uwsgi_routing_func_rpc_raw(struct wsgi_request *wsgi_req, struct uwsgi_route *ur) {
        int ret = -1;
        // this is the list of args
        char *argv[UMAX8];
        // this is the size of each argument
        uint16_t argvs[UMAX8];
        // this is a placeholder for tmp uwsgi_buffers
        struct uwsgi_buffer *ubs[UMAX8];

        char **r_argv = (char **) ur->data2;
        uint16_t *r_argvs = (uint16_t *) ur->data3;

        char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

        uint64_t i;
        for(i=0;i<ur->custom;i++) {
                ubs[i] = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, r_argv[i], r_argvs[i]);
                if (!ubs[i]) goto end;
                argv[i] = ubs[i]->buf;
                argvs[i] = ubs[i]->pos;
        }

        // ok we now need to check it it is a local call or a remote one
        char *func = uwsgi_str(ur->data);
        char *remote = NULL;
        char *at = strchr(func, '@');
        if (at) {
                *at = 0;
                remote = at+1;
        }
        uint64_t size;
        char *response = uwsgi_do_rpc(remote, func, ur->custom, argv, argvs, &size);
        free(func);
        if (!response) goto end;

        ret = UWSGI_ROUTE_NEXT;
	if (size == 0) goto end;

	ret = uwsgi_blob_to_response(wsgi_req, response, size);
        free(response);
	if (ret == 0) {
		ret = UWSGI_ROUTE_BREAK;
	}

end:
        for(i=0;i<ur->custom;i++) {
                if (ubs[i] != NULL) {
                        uwsgi_buffer_destroy(ubs[i]);
                }
        }
        return ret;
}

static int uwsgi_routing_func_rpc_var(struct wsgi_request *wsgi_req, struct uwsgi_route *ur) {
        int ret = -1;
        // this is the list of args
        char *argv[UMAX8];
        // this is the size of each argument
        uint16_t argvs[UMAX8];
        // this is a placeholder for tmp uwsgi_buffers
        struct uwsgi_buffer *ubs[UMAX8];

        char **r_argv = (char **) ur->data2;
        uint16_t *r_argvs = (uint16_t *) ur->data3;

        char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

        uint64_t i;
        for(i=0;i<ur->custom;i++) {
                ubs[i] = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, r_argv[i], r_argvs[i]);
                if (!ubs[i]) goto end;
                argv[i] = ubs[i]->buf;
                argvs[i] = ubs[i]->pos;
        }

        // ok we now need to check it it is a local call or a remote one
        char *func = uwsgi_str(ur->data);
        char *remote = NULL;
        char *at = strchr(func, '@');
        if (at) {
                *at = 0;
                remote = at+1;
        }
        uint64_t size;
        char *response = uwsgi_do_rpc(remote, func, ur->custom, argv, argvs, &size);
        free(func);
        if (!response) goto end;

        ret = UWSGI_ROUTE_BREAK;
        if (size == 0) goto end;

	if (!uwsgi_req_append(wsgi_req, ur->data4, ur->data4_len, response, size)) {
		free(response);
		goto end;
	}
	free(response);
	ret = UWSGI_ROUTE_NEXT;
end:
        for(i=0;i<ur->custom;i++) {
                if (ubs[i] != NULL) {
                        uwsgi_buffer_destroy(ubs[i]);
                }
        }
        return ret;
}



// "next" || "continue" || "break(.*)" || "goon" || "goto .+"
static int uwsgi_routing_func_rpc_ret(struct wsgi_request *wsgi_req, struct uwsgi_route *ur) {
        int ret = -1;
        // this is the list of args
        char *argv[UMAX8];
        // this is the size of each argument
        uint16_t argvs[UMAX8];
        // this is a placeholder for tmp uwsgi_buffers
        struct uwsgi_buffer *ubs[UMAX8];

        char **r_argv = (char **) ur->data2;
        uint16_t *r_argvs = (uint16_t *) ur->data3;

        char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

        uint64_t i;
        for(i=0;i<ur->custom;i++) {
                ubs[i] = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, r_argv[i], r_argvs[i]);
                if (!ubs[i]) goto end;
                argv[i] = ubs[i]->buf;
                argvs[i] = ubs[i]->pos;
        }

        // ok we now need to check it it is a local call or a remote one
        char *func = uwsgi_str(ur->data);
        char *remote = NULL;
        char *at = strchr(func, '@');
        if (at) {
                *at = 0;
                remote = at+1;
        }
        uint64_t size;
        char *response = uwsgi_do_rpc(remote, func, ur->custom, argv, argvs, &size);
        free(func);
        if (!response) goto end;

        ret = UWSGI_ROUTE_CONTINUE;
	if (!uwsgi_strncmp(response, size, "next", 4 )) {
        	ret = UWSGI_ROUTE_NEXT;
	}
	else if (!uwsgi_strncmp(response, size, "continue", 8 )) {
        	ret = UWSGI_ROUTE_CONTINUE;
	}
	else if (!uwsgi_starts_with(response, size, "break", 5 )) {
        	ret = UWSGI_ROUTE_BREAK;
		if (size > 6) {
			if (uwsgi_response_prepare_headers(wsgi_req, response+6, size-6)) goto end0;
                	if (uwsgi_response_add_connection_close(wsgi_req)) goto end0;
                	if (uwsgi_response_add_content_type(wsgi_req, "text/plain", 10)) goto end0;
                	// no need to check for return value
                	uwsgi_response_write_headers_do(wsgi_req);
		}
	}
	else if (!uwsgi_starts_with(response, size, "goto ", 5)) {
		ret = UWSGI_ROUTE_BREAK;
		if (size > 5) {
		 	// find the label
        		struct uwsgi_route *routes = uwsgi.routes;
        		while(routes) {
                		if (!routes->label) goto next;
                		if (!uwsgi_strncmp(routes->label, routes->label_len, response+5, size-5)) {
					ret = UWSGI_ROUTE_NEXT;
                        		wsgi_req->route_goto = routes->pos;
                        		goto found;
                		}
next:
               			routes = routes->next;
        		}
			goto end0;	
found:
        		if (wsgi_req->route_goto <= wsgi_req->route_pc) {
                		wsgi_req->route_goto = 0;
                		uwsgi_log("[uwsgi-route] ERROR \"goto\" instruction can only jump forward (check your label !!!)\n");
				ret = UWSGI_ROUTE_BREAK;
        		}
		}
	}

end0:
        free(response);

end:
        for(i=0;i<ur->custom;i++) {
                if (ubs[i] != NULL) {
                        uwsgi_buffer_destroy(ubs[i]);
                }
        }
        return ret;
}


/*
	ur->data = the func name
        ur->custom = the number of arguments
	ur->data2 = the pointer to the args
	ur->data3 = the pointer to the args sizes
	ur->data4 = func specific
*/
static int uwsgi_router_rpc_base(struct uwsgi_route *ur, char *args) {
	ur->custom = 0;
	ur->data2 = uwsgi_calloc(sizeof(char *) * UMAX8);
	ur->data3 = uwsgi_calloc(sizeof(uint16_t) * UMAX8);
	char *p, *ctx = NULL;
	uwsgi_foreach_token(args, " ", p, ctx) {
		if (!ur->data) {
			ur->data = p;
		}
		else {
			if (ur->custom >= UMAX8) {
				uwsgi_log("unable to register route: maximum number of rpc args reached\n");
				free(ur->data2);
				free(ur->data3);
				return -1;
			}
			char **argv = (char **) ur->data2;
			uint16_t *argvs = (uint16_t *) ur->data3;
			argv[ur->custom] = p;
			argvs[ur->custom] = strlen(p);
			ur->custom++;	
		}
	}

	if (!ur->data) {
		uwsgi_log("unable to register route: you need to specify an rpc function\n");
		free(ur->data2);
		free(ur->data3);
		return -1;
	}
	return 0;
}

static int uwsgi_router_rpc(struct uwsgi_route *ur, char *args) {
        ur->func = uwsgi_routing_func_rpc;
	return uwsgi_router_rpc_base(ur, args);
}

static int uwsgi_router_rpc_ret(struct uwsgi_route *ur, char *args) {
        ur->func = uwsgi_routing_func_rpc_ret;
	return uwsgi_router_rpc_base(ur, args);
}

static int uwsgi_router_rpc_blob(struct uwsgi_route *ur, char *args) {
        ur->func = uwsgi_routing_func_rpc_blob;
        return uwsgi_router_rpc_base(ur, args);
}

static int uwsgi_router_rpc_raw(struct uwsgi_route *ur, char *args) {
        ur->func = uwsgi_routing_func_rpc_raw;
        return uwsgi_router_rpc_base(ur, args);
}

static int uwsgi_router_rpc_var(struct uwsgi_route *ur, char *args) {
        ur->func = uwsgi_routing_func_rpc_var;
	char *space = strchr(args, ' ');
	if (!space) return -1;
	ur->data4 = args;
	ur->data4_len = space - args;
        return uwsgi_router_rpc_base(ur, space+1);
}

static void router_rpc_register() {
        uwsgi_register_router("call", uwsgi_router_rpc);
        uwsgi_register_router("rpc", uwsgi_router_rpc);
        uwsgi_register_router("rpcret", uwsgi_router_rpc_ret);
        uwsgi_register_router("rpcblob", uwsgi_router_rpc_blob);
        uwsgi_register_router("rpcnext", uwsgi_router_rpc_blob);
        uwsgi_register_router("rpcraw", uwsgi_router_rpc_raw);
        uwsgi_register_router("rpcvar", uwsgi_router_rpc_var);
}
#endif

struct uwsgi_plugin rpc_plugin = {

	.name = "rpc",
	.modifier1 = 173,
	
	.request = uwsgi_rpc_request,
#ifdef UWSGI_ROUTING
	.on_load = router_rpc_register,
#endif
};
