1 /*
2  * Copyright (C) 2012 FRAFOS GmbH
3  *
4  * Development sponsored by Sipwise GmbH.
5  *
6  * This file is part of SEMS, a free SIP media server.
7  *
8  * SEMS is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version. This program is released under
12  * the GPL with the additional exemption that compiling, linking,
13  * and/or using OpenSSL is allowed.
14  *
15  * For a license to use the SEMS software under conditions
16  * other than those described here, or to purchase support for this
17  * software, please contact iptel.org by e-mail at the following addresses:
18  *    info@iptel.org
19  *
20  * SEMS is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, write to the Free Software
27  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28  */
29 #include "ModXml.h"
30 #include "log.h"
31 #include "AmUtils.h"
32 
33 SC_EXPORT(MOD_CLS_NAME);
34 
35 void xml_err_func(void *ctx, const char *msg, ...);
36 xmlGenericErrorFunc handler = (xmlGenericErrorFunc)xml_err_func;
37 int xml_log_level = L_ERR;
38 
preload()39 int MOD_CLS_NAME::preload() {
40   DBG("initializing libxml2...\n");
41   xmlInitParser();
42   initGenericErrorDefaultFunc(&handler);
43   handler = (xmlGenericErrorFunc)xml_err_func;
44   xmlSetGenericErrorFunc(NULL, &xml_err_func);
45   xmlKeepBlanksDefault(0);
46   xmlIndentTreeOutput = 1; // doesn't seem to have effect :/
47   return 0;
48 }
49 
MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME)50 MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {
51   DEF_CMD("xml.parse", MODXMLParseAction);
52   DEF_CMD("xml.parseSIPMsgBody", MODXMLParseSIPMsgBodyAction);
53 
54   DEF_CMD("xml.evalXPath", MODXMLEvalXPathAction);
55   DEF_CMD("xml.XPathResultCount", MODXMLXPathResultNodeCount);
56   DEF_CMD("xml.getXPathResult", MODXMLgetXPathResult);
57   DEF_CMD("xml.printXPathResult", MODXMLprintXPathResult);
58   DEF_CMD("xml.updateXPathResult", MODXMLupdateXPathResult);
59 
60   DEF_CMD("xml.docDump", MODXMLdocDump);
61 
62   DEF_CMD("xml.setLoglevel", MODXMLSetLogLevelAction);
63 
64 } MOD_ACTIONEXPORT_END;
65 
66 MOD_CONDITIONEXPORT_NONE(MOD_CLS_NAME);
67 
~ModXmlDoc()68 ModXmlDoc::~ModXmlDoc() {
69   if (NULL != doc) {
70     DBG("freeing XML document [%p]\n", doc);
71     xmlFreeDoc(doc);
72   }
73 }
74 
~ModXmlXPathObj()75 ModXmlXPathObj::~ModXmlXPathObj() {
76   if (NULL != xpathObj) {
77     DBG("freeing XML xpath obj [%p]\n", xpathObj);
78     xmlXPathFreeObject(xpathObj);
79   }
80   if (NULL != xpathCtx) {
81     DBG("freeing XML xpath ctx [%p]\n", xpathCtx);
82     xmlXPathFreeContext(xpathCtx);
83   }
84 }
85 
86 #define TMP_BUF_SIZE 256
xml_err_func(void * ctx,const char * msg,...)87 void xml_err_func(void *ctx, const char *msg, ...) {
88    char _string[TMP_BUF_SIZE];
89    va_list arg_ptr;
90    va_start(arg_ptr, msg);
91    vsnprintf(_string, TMP_BUF_SIZE, msg, arg_ptr);
92    va_end(arg_ptr);
93 
94    _LOG(xml_log_level, "%s", _string);
95 }
96 
97 CONST_ACTION_2P(MODXMLParseSIPMsgBodyAction, ',', false);
EXEC_ACTION_START(MODXMLParseSIPMsgBodyAction)98 EXEC_ACTION_START(MODXMLParseSIPMsgBodyAction) {
99   string msgbody_var = resolveVars(par1, sess, sc_sess, event_params);
100   string dstname = resolveVars(par2, sess, sc_sess, event_params);
101   AVarMapT::iterator it = sc_sess->avar.find(msgbody_var);
102   if (it==sc_sess->avar.end()) {
103     DBG("no message body in avar '%s'\n", msgbody_var.c_str());
104     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
105     sc_sess->SET_STRERROR("no message body in avar " + msgbody_var);
106     EXEC_ACTION_STOP;
107   }
108   AmMimeBody* msgbody = dynamic_cast<AmMimeBody*>(it->second.asObject());
109   if (NULL == msgbody) {
110     DBG("no AmMimeBody in avar '%s'\n", msgbody_var.c_str());
111     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
112     sc_sess->SET_STRERROR("no AmMimeBody in avar " + msgbody_var);
113     EXEC_ACTION_STOP;
114   }
115   const unsigned char* b =  msgbody->getPayload();
116   if (b==NULL) {
117     DBG("empty AmMimeBody in avar '%s'\n", msgbody_var.c_str());
118     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
119     sc_sess->SET_STRERROR("no AmMimeBody in avar " + msgbody_var);
120     EXEC_ACTION_STOP;
121   }
122 
123   xmlSetGenericErrorFunc(NULL, &xml_err_func);
124 
125   xmlDocPtr doc =
126     xmlReadMemory((const char*)b, msgbody->getLen(), "noname.xml", NULL, 0);
127   if (doc == NULL) {
128     DBG("failed parsing XML document from '%s'\n", msgbody_var.c_str());
129     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
130     sc_sess->SET_STRERROR("failed parsing XML document from " + msgbody_var);
131     EXEC_ACTION_STOP;
132   }
133 
134   xmlSetGenericErrorFunc(doc, &xml_err_func);
135 
136   ModXmlDoc* xml_doc = new ModXmlDoc(doc);
137   sc_sess->avar[dstname] = xml_doc;
138   DBG("parsed XML body document to '%s'\n", dstname.c_str());
139 
140 //  string basedir = resolveVars(par2, sess, sc_sess, event_params);
141 } EXEC_ACTION_END;
142 
143 CONST_ACTION_2P(MODXMLParseAction, ',', false);
EXEC_ACTION_START(MODXMLParseAction)144 EXEC_ACTION_START(MODXMLParseAction) {
145   string xml_doc = resolveVars(par1, sess, sc_sess, event_params);
146   string dstname = resolveVars(par2, sess, sc_sess, event_params);
147 
148   xmlSetGenericErrorFunc(NULL, &xml_err_func);
149 
150   xmlDocPtr doc =
151     xmlReadMemory(xml_doc.c_str(), xml_doc.length(), "noname.xml", NULL, 0);
152   if (doc == NULL) {
153     DBG("failed parsing XML document from '%s'\n", xml_doc.c_str());
154     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
155     sc_sess->SET_STRERROR("failed parsing XML document from " + xml_doc);
156     EXEC_ACTION_STOP;
157   }
158   xmlSetGenericErrorFunc(doc, &xml_err_func);
159 
160   ModXmlDoc* xml_doc_var = new ModXmlDoc(doc);
161   sc_sess->avar[dstname] = xml_doc_var;
162   DBG("parsed XML body document to '%s'\n", dstname.c_str());
163 } EXEC_ACTION_END;
164 
165 template<class T>
getXMLElemFromVariable(DSMSession * sc_sess,const string & var_name)166 T* getXMLElemFromVariable(DSMSession* sc_sess, const string& var_name) {
167   AVarMapT::iterator it = sc_sess->avar.find(var_name);
168   if (it == sc_sess->avar.end()) {
169     DBG("object '%s' not found\n", var_name.c_str());
170     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
171     sc_sess->SET_STRERROR("object '"+var_name+"' not found\n");
172     return NULL;
173   }
174 
175   T* doc = dynamic_cast<T*>(it->second.asObject());
176   if (NULL == doc) {
177     DBG("object '%s' is not the right type\n", var_name.c_str());
178     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
179     sc_sess->SET_STRERROR("object '"+var_name+"' is not the right type\n");
180     return NULL;
181   }
182   return doc;
183 }
184 
185 CONST_ACTION_2P(MODXMLEvalXPathAction, ',', false);
EXEC_ACTION_START(MODXMLEvalXPathAction)186 EXEC_ACTION_START(MODXMLEvalXPathAction) {
187   string xpath_expr  = resolveVars(par1, sess, sc_sess, event_params);
188   string xml_doc_var = resolveVars(par2, sess, sc_sess, event_params);
189 
190   xmlSetGenericErrorFunc(NULL, &xml_err_func);
191 
192   ModXmlDoc* xml_doc = getXMLElemFromVariable<ModXmlDoc>(sc_sess, xml_doc_var);
193   if (NULL == xml_doc)
194     EXEC_ACTION_STOP;
195 
196   xmlDocPtr doc = xml_doc->doc;
197 
198   xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
199   if(xpathCtx == NULL) {
200     DBG("unable to create new XPath context\n");
201     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
202     sc_sess->SET_STRERROR("unable to create new XPath context");
203     EXEC_ACTION_STOP;
204   }
205   xmlSetGenericErrorFunc(xpathCtx, &xml_err_func);
206 
207   string xml_doc_ns = sc_sess->var[xml_doc_var+".ns"];
208   vector<string> ns_entries = explode(xml_doc_ns, " ");
209   for (vector<string>::iterator it=ns_entries.begin(); it != ns_entries.end(); it++) {
210     vector<string> ns = explode(*it, "=");
211     if (ns.size() != 2) {
212       DBG("script writer error: namespace entry must be prefix=href (got '%s')\n",
213 	  it->c_str());
214       sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
215       sc_sess->SET_STRERROR("script writer error: namespace entry must be prefix=href\n");
216       xmlXPathFreeContext(xpathCtx);
217       EXEC_ACTION_STOP;
218     }
219 
220     if(xmlXPathRegisterNs(xpathCtx, (const xmlChar*)ns[0].c_str(),
221 			  (const xmlChar*)ns[1].c_str()) != 0) {
222       DBG("unable to register namespace %s=%s\n", ns[0].c_str(), ns[1].c_str());
223       sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
224       sc_sess->SET_STRERROR("unable to register namespace\n");
225       xmlXPathFreeContext(xpathCtx);
226       EXEC_ACTION_STOP;
227     }
228     DBG("registered namespace %s=%s\n", ns[0].c_str(), ns[1].c_str());
229   }
230 
231   xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression((const xmlChar*)xpath_expr.c_str(),
232 						      xpathCtx);
233   if(xpathObj == NULL) {
234     DBG("unable to evaluate xpath expression \"%s\"\n", xpath_expr.c_str());
235     sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
236     sc_sess->SET_STRERROR("unable to evaluate xpath expression");
237     xmlXPathFreeContext(xpathCtx);
238     EXEC_ACTION_STOP;
239   }
240 
241   ModXmlXPathObj* xpath_obj = new ModXmlXPathObj(xpathObj, xpathCtx);
242   sc_sess->avar[xml_doc_var+".xpath"] = xpath_obj;
243   DBG("evaluated XPath expression on '%s' to '%s'\n",
244       xml_doc_var.c_str(), (xml_doc_var+".xpath").c_str());
245 
246 } EXEC_ACTION_END;
247 
248 CONST_ACTION_2P(MODXMLXPathResultNodeCount, '=', false);
EXEC_ACTION_START(MODXMLXPathResultNodeCount)249 EXEC_ACTION_START(MODXMLXPathResultNodeCount) {
250   string cnt_var  = par1;
251   string xpath_res_var = resolveVars(par2, sess, sc_sess, event_params);
252 
253   if (cnt_var.size() && cnt_var[0]=='$') {
254     cnt_var.erase(0,1);
255   }
256 
257   ModXmlXPathObj* xpath_obj =
258     getXMLElemFromVariable<ModXmlXPathObj>(sc_sess, xpath_res_var);
259   if (NULL == xpath_obj){
260     DBG("no xpath result found in '%s'\n", xpath_res_var.c_str());
261     sc_sess->var[cnt_var] = "0";
262     EXEC_ACTION_STOP;
263   }
264 
265   unsigned int res = (xpath_obj->xpathObj->nodesetval) ?
266     xpath_obj->xpathObj->nodesetval->nodeNr : 0;
267 
268   sc_sess->var[cnt_var] = int2str(res);
269   DBG("set count $%s=%u\n", cnt_var.c_str(), res);
270 
271 } EXEC_ACTION_END;
272 
273 
274 CONST_ACTION_2P(MODXMLgetXPathResult, '=', false);
EXEC_ACTION_START(MODXMLgetXPathResult)275 EXEC_ACTION_START(MODXMLgetXPathResult) {
276   string cnt_var  = par1;
277   string xpath_res_var = resolveVars(par2, sess, sc_sess, event_params);
278 
279   if (cnt_var.size() && cnt_var[0]=='$') {
280     cnt_var.erase(0,1);
281   }
282 
283   ModXmlXPathObj* xpath_obj =
284     getXMLElemFromVariable<ModXmlXPathObj>(sc_sess, xpath_res_var);
285   if (NULL == xpath_obj){
286     DBG("no xpath result found in '%s'\n", xpath_res_var.c_str());
287     sc_sess->var[cnt_var] = "0";
288     EXEC_ACTION_STOP;
289   }
290 
291   vector<string> res;
292 
293   if (NULL == xpath_obj->xpathObj->nodesetval){
294     res.push_back(string());
295   } else {
296     xmlNodeSetPtr nodes = xpath_obj->xpathObj->nodesetval;
297     xmlNodePtr cur;
298 
299     for (int i=0;i<xpath_obj->xpathObj->nodesetval->nodeNr;i++) {
300       if(nodes->nodeTab[i]->type == XML_NAMESPACE_DECL) {
301 	xmlNsPtr ns;
302 
303 	ns = (xmlNsPtr)nodes->nodeTab[i];
304 	cur = (xmlNodePtr)ns->next;
305 	res.push_back(string(string((const char*) ns->prefix)+"="+string((const char*) ns->href)));
306 
307       } else if(nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
308 	cur = nodes->nodeTab[i];
309 	xmlChar* c = xmlNodeGetContent(cur);
310 	res.push_back(c ? string((const char*)c) : string());
311       } else {
312 	cur = nodes->nodeTab[i];
313 	res.push_back(string((const char*) cur->name)+"\": type "+int2str(cur->type));
314       }
315     }
316   }
317 
318   if (res.size() == 1) {
319     sc_sess->var[cnt_var] = res[0];
320     DBG("set $%s='%s'\n", cnt_var.c_str(), res[0].c_str());
321   } else {
322     unsigned int p =0;
323     for (vector<string>::iterator it = res.begin(); it!= res.end(); it++) {
324       sc_sess->var[cnt_var+"["+int2str(p)+"]"] = *it;
325       DBG("set $%s='%s'\n", (cnt_var+"["+int2str(p)+"]").c_str(), it->c_str());
326       p++;
327     }
328   }
329 
330 
331 } EXEC_ACTION_END;
332 
333 CONST_ACTION_2P(MODXMLprintXPathResult, '=', false);
EXEC_ACTION_START(MODXMLprintXPathResult)334 EXEC_ACTION_START(MODXMLprintXPathResult) {
335   string cnt_var  = par1;
336   string xpath_res_var = resolveVars(par2, sess, sc_sess, event_params);
337 
338   if (cnt_var.size() && cnt_var[0]=='$') {
339     cnt_var.erase(0,1);
340   }
341 
342   ModXmlXPathObj* xpath_obj =
343     getXMLElemFromVariable<ModXmlXPathObj>(sc_sess, xpath_res_var);
344   if (NULL == xpath_obj){
345     DBG("no xpath result found in '%s'\n", xpath_res_var.c_str());
346     sc_sess->var[cnt_var] = "0";
347     EXEC_ACTION_STOP;
348   }
349 
350   string& res = sc_sess->var[cnt_var];
351   if (NULL == xpath_obj->xpathObj->nodesetval){
352     res = "";
353   } else {
354     xmlNodeSetPtr nodes = xpath_obj->xpathObj->nodesetval;
355     xmlNodePtr cur;
356 
357     for (int i=0;i<xpath_obj->xpathObj->nodesetval->nodeNr;i++) {
358       if(nodes->nodeTab[i]->type == XML_NAMESPACE_DECL) {
359 	xmlNsPtr ns;
360 
361 	ns = (xmlNsPtr)nodes->nodeTab[i];
362 	cur = (xmlNodePtr)ns->next;
363 	if(cur->ns) {
364 	  res += "namespace \""+string((const char*) ns->prefix)+"\"=\""+string((const char*) ns->href)+"\" for node "+
365 	    string((const char*) cur->ns->href)+":"+string((const char*) cur->name)+"\n";
366 	} else {
367 	  res += "namespace \""+ string((const char*) ns->prefix) +"\"=\""+ string((const char*) ns->href) +"\" for node "+
368 	    string((const char*) cur->name)+"\n";
369 	}
370       } else if(nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
371 	cur = nodes->nodeTab[i];
372 	if(cur->ns) {
373 	  xmlChar* c = xmlNodeGetContent(cur);
374 	  res += "element node \""+string((const char*) cur->ns->href)+":"+string((const char*) cur->name)+"\" content: \""+
375 	    (c?string((const char*)c):string("NULL"))+ "\"\n";
376 	} else {
377 	  xmlChar* c = xmlNodeGetContent(cur);
378 	  res += "element node \""+string((const char*) cur->name)+"\" content: \""+ (c?string((const char*)c):string("NULL"))+"\n";
379 	}
380       } else {
381 	cur = nodes->nodeTab[i];
382 	res += "node \""+string((const char*) cur->name)+"\": type "+int2str(cur->type)+"\n";
383       }
384     }
385   }
386 
387   DBG("set $%s='%s'\n", cnt_var.c_str(), res.c_str());
388 
389 } EXEC_ACTION_END;
390 
391 
392 /**
393    modified from  http://www.xmlsoft.org/examples/xpath2.c (MIT license)
394  * update_xpath_nodes:
395  * @nodes:		the nodes set.
396  * @value:		the new value for the node(s)
397  *
398  * Prints the @nodes content to @output.
399  */
400 static void
update_xpath_nodes(xmlNodeSetPtr nodes,const xmlChar * value,int index)401 update_xpath_nodes(xmlNodeSetPtr nodes, const xmlChar* value, int index) {
402     int size;
403     int i;
404 
405     assert(value);
406     size = (nodes) ? nodes->nodeNr : 0;
407 
408     if (index < 0) {
409       // update all
410       /*
411        * NOTE: the nodes are processed in reverse order, i.e. reverse document
412        *       order because xmlNodeSetContent can actually free up descendant
413        *       of the node and such nodes may have been selected too ! Handling
414        *       in reverse order ensure that descendant are accessed first, before
415        *       they get removed. Mixing XPath and modifications on a tree must be
416        *       done carefully !
417        */
418       for(i = size - 1; i >= 0; i--) {
419 	if (NULL == nodes->nodeTab[i])
420 	  continue;
421 
422 	xmlNodeSetContent(nodes->nodeTab[i], value);
423 	/*
424 	 * All the elements returned by an XPath query are pointers to
425 	 * elements from the tree *except* namespace nodes where the XPath
426 	 * semantic is different from the implementation in libxml2 tree.
427 	 * As a result when a returned node set is freed when
428 	 * xmlXPathFreeObject() is called, that routine must check the
429 	 * element type. But node from the returned set may have been removed
430 	 * by xmlNodeSetContent() resulting in access to freed data.
431 	 * This can be exercised by running
432 	 *       valgrind xpath2 test3.xml '//discarded' discarded
433 	 * There is 2 ways around it:
434 	 *   - make a copy of the pointers to the nodes from the result set
435 	 *     then call xmlXPathFreeObject() and then modify the nodes
436 	 * or
437 	 *   - remove the reference to the modified nodes from the node set
438 	 *     as they are processed, if they are not namespace nodes.
439 	 */
440 	if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL)
441 	  nodes->nodeTab[i] = NULL;
442       }
443     } else {
444       if (index >= size) {
445 	ERROR("trying to update XML node %d, size is %d\n", index, size);
446 	return;
447       }
448 
449       if (NULL == nodes->nodeTab[index]) {
450 	ERROR("trying to update XML node %d which is NULL\n", index);
451       }
452 
453       xmlNodeSetContent(nodes->nodeTab[index], value);
454       /*
455        * All the elements returned by an XPath query are pointers to
456        * elements from the tree *except* namespace nodes where the XPath
457        * semantic is different from the implementation in libxml2 tree.
458        * As a result when a returned node set is freed when
459        * xmlXPathFreeObject() is called, that routine must check the
460        * element type. But node from the returned set may have been removed
461        * by xmlNodeSetContent() resulting in access to freed data.
462        * This can be exercised by running
463        *       valgrind xpath2 test3.xml '//discarded' discarded
464        * There is 2 ways around it:
465        *   - make a copy of the pointers to the nodes from the result set
466        *     then call xmlXPathFreeObject() and then modify the nodes
467        * or
468        *   - remove the reference to the modified nodes from the node set
469        *     as they are processed, if they are not namespace nodes.
470        */
471       if (nodes->nodeTab[index]->type != XML_NAMESPACE_DECL)
472 	nodes->nodeTab[index] = NULL;
473     }
474 }
475 
476 CONST_ACTION_2P(MODXMLupdateXPathResult, '=', false);
EXEC_ACTION_START(MODXMLupdateXPathResult)477 EXEC_ACTION_START(MODXMLupdateXPathResult) {
478   string xpath_res_var = resolveVars(par1, sess, sc_sess, event_params);
479   string value  = resolveVars(par2, sess, sc_sess, event_params);
480 
481   // support index
482   int index = -1;
483   if (xpath_res_var.size()>2 && xpath_res_var[xpath_res_var.size()-1]==']') {
484     size_t p = xpath_res_var.rfind('[');
485     if (p != string::npos) {
486       str2int(xpath_res_var.substr(p+1, xpath_res_var.size()-p-2), index);
487       xpath_res_var.erase(p);
488     }
489   }
490 
491   DBG("index %d, var '%s'\n", index, xpath_res_var.c_str());
492   ModXmlXPathObj* xpath_obj =
493     getXMLElemFromVariable<ModXmlXPathObj>(sc_sess, xpath_res_var);
494   if (NULL == xpath_obj){
495     DBG("no xpath result found in '%s'\n", xpath_res_var.c_str());
496     EXEC_ACTION_STOP;
497   }
498   // todo: call xmlEncodeSpecialChars with doc
499 
500 
501   update_xpath_nodes(xpath_obj->xpathObj->nodesetval, (const xmlChar*) value.c_str(), index);
502 
503 } EXEC_ACTION_END;
504 
505 CONST_ACTION_2P(MODXMLdocDump, '=', false);
EXEC_ACTION_START(MODXMLdocDump)506 EXEC_ACTION_START(MODXMLdocDump) {
507   string res_var  = par1;
508   string xml_doc_var = resolveVars(par2, sess, sc_sess, event_params);
509 
510   if (res_var.size() && res_var[0]=='$') {
511     res_var.erase(0,1);
512   }
513 
514   ModXmlDoc* xml_doc = getXMLElemFromVariable<ModXmlDoc>(sc_sess, xml_doc_var);
515   if (NULL == xml_doc) {
516     DBG("XML document not found t variable '%s'\n", xml_doc_var.c_str());
517     sc_sess->var[res_var] = "";
518     EXEC_ACTION_STOP;
519   }
520 
521   xmlChar* mem;
522   int size;
523   xmlDocDumpFormatMemory(xml_doc->doc, &mem, &size, /* indent=*/1);
524   sc_sess->var[res_var] = string((const char*)mem, size);
525   xmlFree(mem);
526   DBG("set $%s to XML of size %d\n", res_var.c_str(), size);
527 
528 } EXEC_ACTION_END;
529 
530 
EXEC_ACTION_START(MODXMLSetLogLevelAction)531 EXEC_ACTION_START(MODXMLSetLogLevelAction) {
532   string xml_log_level_s = resolveVars(arg, sess, sc_sess, event_params);
533   if (xml_log_level_s == "error")
534     xml_log_level = L_ERR;
535   else if (xml_log_level_s == "warn")
536     xml_log_level = L_WARN;
537   else if (xml_log_level_s == "info")
538     xml_log_level = L_INFO;
539   else if (xml_log_level_s == "debug")
540     xml_log_level = L_DBG;
541   else {
542     ERROR("script writer error: '%s' is no valid log level (error, warn, info, debug)\n",
543 	  xml_log_level_s.c_str());
544   }
545 } EXEC_ACTION_END;
546