gnucash/libgnucash/backend/xml/sixtp-dom-parsers.cpp
Geert Janssens 48b29f5e91 Fix memory leak in char* type KvpValue and fix improper uses
The core issue was that the delete visitor was never called because its parameter
type (char *) didn't match the boost::variant type (const char *).
Fixing the visitor's parameter type also require a const_cast
back to char * because that's what g_free takes as argument.

The rest of this commit is merely fixing KvpValue instantiations that
tried to create a char* KvpValue from a stack based const string instead
of a heap allocated one. That would bomb out on calling the
delete visitor.
2018-09-10 19:49:43 +02:00

866 lines
20 KiB
C++

/********************************************************************
* sixtp-dom-parsers.c *
* Copyright 2001 Gnumatic, Inc. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
* *
********************************************************************/
extern "C"
{
#include <config.h>
#include <glib.h>
#include <string.h>
#include "gnc-xml-helper.h"
#include <gnc-engine.h>
}
#include "sixtp-utils.h"
#include "sixtp-dom-parsers.h"
#include <kvp-frame.hpp>
static QofLogModule log_module = GNC_MOD_IO;
GncGUID*
dom_tree_to_guid (xmlNodePtr node)
{
if (!node->properties)
{
return NULL;
}
if (strcmp ((char*) node->properties->name, "type") != 0)
{
PERR ("Unknown attribute for id tag: %s",
node->properties->name ?
(char*) node->properties->name : "(null)");
return NULL;
}
{
char* type;
type = (char*)xmlNodeGetContent (node->properties->xmlAttrPropertyValue);
/* handle new and guid the same for the moment */
if ((g_strcmp0 ("guid", type) == 0) || (g_strcmp0 ("new", type) == 0))
{
auto gid = guid_new ();
char* guid_str;
guid_str = (char*)xmlNodeGetContent (node->xmlChildrenNode);
string_to_guid (guid_str, gid);
xmlFree (guid_str);
xmlFree (type);
return gid;
}
else
{
PERR ("Unknown type %s for attribute type for tag %s",
type ? type : "(null)",
node->properties->name ?
(char*) node->properties->name : "(null)");
xmlFree (type);
return NULL;
}
}
}
static KvpValue*
dom_tree_to_integer_kvp_value (xmlNodePtr node)
{
gchar* text;
gint64 daint;
KvpValue* ret = NULL;
text = dom_tree_to_text (node);
if (string_to_gint64 (text, &daint))
{
ret = new KvpValue {daint};
}
g_free (text);
return ret;
}
gboolean
dom_tree_to_integer (xmlNodePtr node, gint64* daint)
{
gchar* text;
gboolean ret;
text = dom_tree_to_text (node);
ret = string_to_gint64 (text, daint);
g_free (text);
return ret;
}
gboolean
dom_tree_to_guint16 (xmlNodePtr node, guint16* i)
{
gboolean ret;
guint j = 0;
ret = dom_tree_to_guint (node, &j);
*i = (guint16) j;
return ret;
}
gboolean
dom_tree_to_guint (xmlNodePtr node, guint* i)
{
gchar* text, *endptr;
gboolean ret;
text = dom_tree_to_text (node);
/* In spite of the strange string_to_gint64 function, I'm just
going to use strtoul here until someone shows me the error of
my ways. -CAS */
*i = (guint) strtoul (text, &endptr, 0);
ret = (endptr != text);
g_free (text);
return ret;
}
gboolean
dom_tree_to_boolean (xmlNodePtr node, gboolean* b)
{
gchar* text;
text = dom_tree_to_text (node);
if (g_ascii_strncasecmp (text, "true", 4) == 0)
{
*b = TRUE;
return TRUE;
}
else if (g_ascii_strncasecmp (text, "false", 5) == 0)
{
*b = FALSE;
return TRUE;
}
else
{
*b = FALSE;
return FALSE;
}
}
static KvpValue*
dom_tree_to_double_kvp_value (xmlNodePtr node)
{
gchar* text;
double dadoub;
KvpValue* ret = NULL;
text = dom_tree_to_text (node);
if (string_to_double (text, &dadoub))
{
ret = new KvpValue {dadoub};
}
g_free (text);
return ret;
}
static KvpValue*
dom_tree_to_numeric_kvp_value (xmlNodePtr node)
{
gnc_numeric* danum;
KvpValue* ret = NULL;
danum = dom_tree_to_gnc_numeric (node);
if (danum)
{
ret = new KvpValue {*danum};
}
g_free (danum);
return ret;
}
static KvpValue*
dom_tree_to_string_kvp_value (xmlNodePtr node)
{
const gchar* datext;
KvpValue* ret = NULL;
datext = dom_tree_to_text (node);
if (datext)
{
ret = new KvpValue {datext};
}
return ret;
}
static KvpValue*
dom_tree_to_guid_kvp_value (xmlNodePtr node)
{
GncGUID* daguid;
KvpValue* ret = NULL;
daguid = dom_tree_to_guid (node);
if (daguid)
{
ret = new KvpValue {daguid};
}
return ret;
}
static KvpValue*
dom_tree_to_time64_kvp_value (xmlNodePtr node)
{
Time64 t{dom_tree_to_time64 (node)};
return new KvpValue {t};
}
static KvpValue*
dom_tree_to_gdate_kvp_value (xmlNodePtr node)
{
GDate* date;
KvpValue* ret = NULL;
date = dom_tree_to_gdate (node);
if (date)
{
ret = new KvpValue {*date};
}
g_free (date);
return ret;
}
gboolean
string_to_binary (const gchar* str, void** v, guint64* data_len)
{
guint64 str_len;
guchar* data;
unsigned int i, j;
g_return_val_if_fail (v != NULL, FALSE);
g_return_val_if_fail (data_len != NULL, FALSE);
str_len = strlen (str);
/* Since no whitespace is allowed and hex encoding is 2 text chars
per binary char, the result must be half the input size and the
input size must be even. */
if ((str_len % 2) != 0)
return (FALSE);
*data_len = str_len / 2;
data = g_new0 (guchar, *data_len);
for (j = 0, i = 0; i < str_len; i += 2, j++)
{
gchar tmpstr[3];
long int converted;
tmpstr[0] = str[i];
tmpstr[1] = str[i + 1];
tmpstr[2] = '\0';
converted = strtol (tmpstr, NULL, 16);
data[j] = (unsigned char)converted;
}
*v = data;
return (TRUE);
}
static KvpValue* dom_tree_to_kvp_value (xmlNodePtr node);
//needed for test access as well as internal use.
KvpFrame* dom_tree_to_kvp_frame (xmlNodePtr node);
static KvpValue*
dom_tree_to_list_kvp_value (xmlNodePtr node)
{
GList* list = NULL;
xmlNodePtr mark;
KvpValue* ret = NULL;
for (mark = node->xmlChildrenNode; mark; mark = mark->next)
{
KvpValue* new_val;
if (g_strcmp0 ((char*)mark->name, "text") == 0)
continue;
new_val = dom_tree_to_kvp_value (mark);
if (new_val)
{
list = g_list_append (list, (gpointer)new_val);
}
}
ret = new KvpValue {list};
return ret;
}
static KvpValue*
dom_tree_to_frame_kvp_value (xmlNodePtr node)
{
KvpFrame* frame;
KvpValue* ret = NULL;
frame = dom_tree_to_kvp_frame (node);
if (frame)
{
ret = new KvpValue {frame};
}
return ret;
}
struct kvp_val_converter
{
const gchar* tag;
KvpValue* (*converter) (xmlNodePtr node);
};
/* Note: The type attribute must remain 'timespec' to maintain compatibility.
*/
struct kvp_val_converter val_converters[] =
{
{ "integer", dom_tree_to_integer_kvp_value },
{ "double", dom_tree_to_double_kvp_value },
{ "numeric", dom_tree_to_numeric_kvp_value },
{ "string", dom_tree_to_string_kvp_value },
{ "guid", dom_tree_to_guid_kvp_value },
{ "timespec", dom_tree_to_time64_kvp_value },
{ "gdate", dom_tree_to_gdate_kvp_value },
{ "list", dom_tree_to_list_kvp_value },
{ "frame", dom_tree_to_frame_kvp_value },
{ 0, 0 },
};
static KvpValue*
dom_tree_to_kvp_value (xmlNodePtr node)
{
xmlChar* xml_type;
gchar* type;
struct kvp_val_converter* mark;
KvpValue* ret = NULL;
xml_type = xmlGetProp (node, BAD_CAST "type");
if (xml_type)
{
type = g_strdup ((char*) xml_type);
xmlFree (xml_type);
}
else
type = NULL;
for (mark = val_converters; mark->tag; mark++)
{
if (g_strcmp0 (type, mark->tag) == 0)
{
ret = (mark->converter) (node);
}
}
if (!mark->tag)
{
/* FIXME: deal with unknown type tag here */
}
g_free (type);
return ret;
}
static gboolean
dom_tree_to_kvp_frame_given (xmlNodePtr node, KvpFrame* frame)
{
xmlNodePtr mark;
g_return_val_if_fail (node, FALSE);
g_return_val_if_fail (frame, FALSE);
for (mark = node->xmlChildrenNode; mark; mark = mark->next)
{
if (g_strcmp0 ((char*)mark->name, "slot") == 0)
{
xmlNodePtr mark2;
gchar* key = NULL;
KvpValue* val = NULL;
for (mark2 = mark->xmlChildrenNode; mark2; mark2 = mark2->next)
{
if (g_strcmp0 ((char*)mark2->name, "slot:key") == 0)
{
key = dom_tree_to_text (mark2);
}
else if (g_strcmp0 ((char*)mark2->name, "slot:value") == 0)
{
val = dom_tree_to_kvp_value (mark2);
}
else
{
/* FIXME: should put some error here.
* But ignore text type! */
}
}
if (key)
{
if (val)
{
//We're deleting the old KvpValue returned by replace_nc().
delete frame->set ({key}, val);
}
else
{
/* FIXME: should put some error here */
}
g_free (key);
}
}
}
return TRUE;
}
KvpFrame*
dom_tree_to_kvp_frame (xmlNodePtr node)
{
g_return_val_if_fail (node, NULL);
auto ret = new KvpFrame;
if (dom_tree_to_kvp_frame_given (node, ret))
return ret;
delete ret;
return NULL;
}
gboolean
dom_tree_create_instance_slots (xmlNodePtr node, QofInstance* inst)
{
KvpFrame* frame = qof_instance_get_slots (inst);
return dom_tree_to_kvp_frame_given (node, frame);
}
gchar*
dom_tree_to_text (xmlNodePtr tree)
{
/* Expect *only* text and comment sibling nodes in the given tree --
which actually may only be a "list". i.e. if you're trying to
extract bar from <foo>bar</foo>, pass in <foo>->xmlChildrenNode
to this function. This expectation is different from the rest of
the dom_tree_to_* converters...
Ignores comment nodes and collapse text nodes into one string.
Returns NULL if expectations are unsatisfied.
*/
gchar* result;
gchar* temp;
g_return_val_if_fail (tree, NULL);
/* no nodes means it's an empty string text */
if (!tree->xmlChildrenNode)
{
DEBUG ("No children");
return g_strdup ("");
}
temp = (char*)xmlNodeListGetString (NULL, tree->xmlChildrenNode, TRUE);
if (!temp)
{
DEBUG ("Null string");
return NULL;
}
DEBUG ("node string [%s]", (temp == NULL ? "(null)" : temp));
result = g_strdup (temp);
xmlFree (temp);
return result;
}
gnc_numeric*
dom_tree_to_gnc_numeric (xmlNodePtr node)
{
gchar* content = dom_tree_to_text (node);
if (!content)
return NULL;
gnc_numeric *ret = g_new (gnc_numeric, 1);
if (!string_to_gnc_numeric (content, ret))
*ret = gnc_numeric_zero ();
g_free (content);
return ret;
}
time64
dom_tree_to_time64 (xmlNodePtr node)
{
/* Turn something like this
<date-posted>
<ts:date>Mon, 05 Jun 2000 23:16:19 -0500</ts:date>
</date-posted>
into a time64, returning INT64_MAX that we're using to flag an erroneous
date if there's a problem. Only one ts:date element is permitted for any
date attribute.
*/
time64 ret {INT64_MAX};
gboolean seen = FALSE;
xmlNodePtr n;
for (n = node->xmlChildrenNode; n; n = n->next)
{
switch (n->type)
{
case XML_COMMENT_NODE:
case XML_TEXT_NODE:
break;
case XML_ELEMENT_NODE:
if (g_strcmp0 ("ts:date", (char*)n->name) == 0)
{
if (seen)
{
return INT64_MAX;
}
else
{
gchar* content = dom_tree_to_text (n);
if (!content)
{
return INT64_MAX;
}
ret = gnc_iso8601_to_time64_gmt (content);
g_free (content);
seen = TRUE;
}
}
break;
default:
PERR ("unexpected sub-node.");
return INT64_MAX;
break;
}
}
if (!seen)
{
PERR ("no ts:date node found.");
return INT64_MAX;
}
return ret;
}
GDate*
dom_tree_to_gdate (xmlNodePtr node)
{
/* Turn something like this
<sx:startdate>
<gdate>2001-04-03</gdate>
</sx:startdate>
into a GDate. If the xml is invalid, returns NULL. */
GDate* ret;
gboolean seen_date = FALSE;
xmlNodePtr n;
/* creates an invalid date */
ret = g_date_new ();
for (n = node->xmlChildrenNode; n; n = n->next)
{
switch (n->type)
{
case XML_COMMENT_NODE:
case XML_TEXT_NODE:
break;
case XML_ELEMENT_NODE:
if (g_strcmp0 ("gdate", (char*)n->name) == 0)
{
if (seen_date)
{
goto failure;
}
else
{
gchar* content = dom_tree_to_text (n);
gint year, month, day;
if (!content)
{
goto failure;
}
if (sscanf (content, "%d-%d-%d", &year, &month, &day) != 3)
{
g_free (content);
goto failure;
}
g_free (content);
seen_date = TRUE;
g_date_set_dmy (ret, day, static_cast<GDateMonth> (month),
year);
if (!g_date_valid (ret))
{
PWARN ("invalid date");
goto failure;
}
}
}
break;
default:
PERR ("unexpected sub-node.");
goto failure;
}
}
if (!seen_date)
{
PWARN ("no gdate node found.");
goto failure;
}
return ret;
failure:
g_date_free (ret);
return NULL;
}
gnc_commodity*
dom_tree_to_commodity_ref_no_engine (xmlNodePtr node, QofBook* book)
{
/* Turn something like this
<currency>
<cmdty:space>NASDAQ</cmdty:space>
<cmdty:id>LNUX</cmdty:space>
</currency>
into a gnc_commodity*, returning NULL on failure. Both sub-nodes
are required, though for now, order is irrelevant. */
gnc_commodity* c = NULL;
gchar* space_str = NULL;
gchar* id_str = NULL;
xmlNodePtr n;
if (!node) return NULL;
if (!node->xmlChildrenNode) return NULL;
for (n = node->xmlChildrenNode; n; n = n->next)
{
switch (n->type)
{
case XML_COMMENT_NODE:
case XML_TEXT_NODE:
break;
case XML_ELEMENT_NODE:
if (g_strcmp0 ("cmdty:space", (char*)n->name) == 0)
{
if (space_str)
{
return NULL;
}
else
{
gchar* content = dom_tree_to_text (n);
if (!content) return NULL;
space_str = content;
}
}
else if (g_strcmp0 ("cmdty:id", (char*)n->name) == 0)
{
if (id_str)
{
return NULL;
}
else
{
gchar* content = dom_tree_to_text (n);
if (!content) return NULL;
id_str = content;
}
}
break;
default:
PERR ("unexpected sub-node.");
return NULL;
break;
}
}
if (! (space_str && id_str))
{
c = NULL;
}
else
{
g_strstrip (space_str);
g_strstrip (id_str);
c = gnc_commodity_new (book, NULL, space_str, id_str, NULL, 0);
}
g_free (space_str);
g_free (id_str);
return c;
}
gnc_commodity*
dom_tree_to_commodity_ref (xmlNodePtr node, QofBook* book)
{
gnc_commodity* daref;
gnc_commodity* ret;
gnc_commodity_table* table;
daref = dom_tree_to_commodity_ref_no_engine (node, book);
table = gnc_commodity_table_get_table (book);
g_return_val_if_fail (table != NULL, NULL);
ret = gnc_commodity_table_lookup (table,
gnc_commodity_get_namespace (daref),
gnc_commodity_get_mnemonic (daref));
gnc_commodity_destroy (daref);
g_return_val_if_fail (ret != NULL, NULL);
return ret;
}
/***********************************************************************/
/* generic parser */
static inline void
dom_tree_handlers_reset (struct dom_tree_handler* handlers)
{
for (; handlers->tag != NULL; handlers++)
{
handlers->gotten = 0;
}
}
static inline gboolean
dom_tree_handlers_all_gotten_p (struct dom_tree_handler* handlers)
{
gboolean ret = TRUE;
for (; handlers->tag != NULL; handlers++)
{
if (handlers->required && ! handlers->gotten)
{
PERR ("Not defined and it should be: %s",
handlers->tag ? handlers->tag : "(null)");
ret = FALSE;
}
}
return ret;
}
static inline gboolean
gnc_xml_set_data (const gchar* tag, xmlNodePtr node, gpointer item,
struct dom_tree_handler* handlers)
{
for (; handlers->tag != NULL; handlers++)
{
if (g_strcmp0 (tag, handlers->tag) == 0)
{
(handlers->handler) (node, item);
handlers->gotten = TRUE;
break;
}
}
if (!handlers->tag)
{
PERR ("Unhandled tag: %s",
tag ? tag : "(null)");
return FALSE;
}
return TRUE;
}
gboolean
dom_tree_generic_parse (xmlNodePtr node, struct dom_tree_handler* handlers,
gpointer data)
{
xmlNodePtr achild;
gboolean successful = TRUE;
dom_tree_handlers_reset (handlers);
for (achild = node->xmlChildrenNode; achild; achild = achild->next)
{
/* ignore stray text nodes */
if (g_strcmp0 ((char*)achild->name, "text") == 0)
continue;
if (!gnc_xml_set_data ((char*)achild->name, achild, data, handlers))
{
PERR ("gnc_xml_set_data failed");
successful = FALSE;
continue;
}
}
if (!dom_tree_handlers_all_gotten_p (handlers))
{
PERR ("didn't find all of the expected tags in the input");
successful = FALSE;
}
return successful;
}
gboolean
dom_tree_valid_time64 (time64 val, const xmlChar * name)
{
if (val != INT64_MAX)
return TRUE;
g_warning ("Invalid timestamp in data file. Look for a '%s' entry "
"with a year outside of the valid range: 1400..10000", name);
return FALSE;
}