1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-39124 XMLTYPE: allow only well-formed XML.

Necessary checks added to the XMLTYPE.
This commit is contained in:
Alexey Botchkov
2026-03-31 00:13:43 +04:00
parent aafcc57c25
commit 2ed8c4c812
9 changed files with 280 additions and 57 deletions

View File

@@ -40,6 +40,13 @@ extern "C" {
*/
#define MY_XML_FLAG_SKIP_TEXT_NORMALIZATION 2
/*
A flag if parsing should fail when XML is not
Well Formed as it's described in standards.
Like having exactly one root element an <?xml on top.
*/
#define MY_XML_FLAG_ASSERT_WELL_FORMED 4
enum my_xml_node_type
{
MY_XML_NODE_TAG, /* can have TAG, ATTR and TEXT children */
@@ -52,6 +59,15 @@ typedef struct xml_stack_st
int flags;
enum my_xml_node_type current_node_type;
char errstr[128];
int root_node_counter;
/*
Counts some items to verify that the possible
<?xml..?> instruction is first in the document.
It counts all the root items and some nested that
are easier to count than to detect if they're on the root.
*/
int item_counter;
struct {
char static_buffer[128];
@@ -68,6 +84,10 @@ typedef struct xml_stack_st
int (*enter)(struct xml_stack_st *st,const char *val, size_t len);
int (*value)(struct xml_stack_st *st,const char *val, size_t len);
int (*leave_xml)(struct xml_stack_st *st,const char *val, size_t len);
int (*processing_instruction)(struct xml_stack_st *st,
const char *target, size_t target_len,
const char *value, size_t value_len);
} MY_XML_PARSER;
void my_xml_parser_create(MY_XML_PARSER *st);
@@ -83,6 +103,8 @@ void my_xml_set_enter_handler(MY_XML_PARSER *st, int (*)(MY_XML_PARSER *,
void my_xml_set_leave_handler(MY_XML_PARSER *st, int (*)(MY_XML_PARSER *,
const char *,
size_t len));
void my_xml_set_processing_instruction_handler(MY_XML_PARSER *st,
int (*)(MY_XML_PARSER *, const char *, size_t, const char *, size_t));
void my_xml_set_user_data(MY_XML_PARSER *st, void *);
size_t my_xml_error_pos(MY_XML_PARSER *st);

View File

@@ -5267,9 +5267,9 @@ DROP TABLE t1,t2;
#
# MDEV-18205 Assertion `str_length < len' failed in Binary_string::realloc_raw
#
SELECT GROUP_CONCAT( UpdateXML( '<a>new year</a>', '/a', '2019-01-01 00:00:00' ), ENCODE('text','pass') ) AS f;
SELECT GROUP_CONCAT( UpdateXML( '<a>new year</a>', '/a', '<z>2019-01-01 00:00:00</z>' ), ENCODE('text','pass') ) AS f;
f
2019-01-01 00:00:00F}^i
<z>2019-01-01 00:00:00</z>F}^i
#
# MDEV-31184 Remove parser tokens DECODE_MARIADB_SYM and DECODE_ORACLE_SYM
#

View File

@@ -2314,7 +2314,7 @@ DROP TABLE t1,t2;
--echo # MDEV-18205 Assertion `str_length < len' failed in Binary_string::realloc_raw
--echo #
SELECT GROUP_CONCAT( UpdateXML( '<a>new year</a>', '/a', '2019-01-01 00:00:00' ), ENCODE('text','pass') ) AS f;
SELECT GROUP_CONCAT( UpdateXML( '<a>new year</a>', '/a', '<z>2019-01-01 00:00:00</z>' ), ENCODE('text','pass') ) AS f;
--echo #

View File

@@ -236,7 +236,9 @@ select UpdateXML(@xml, '/a/b/@bb2', 'bb3="bb3"');
#
select updatexml('<div><div><span>1</span><span>2</span></div></div>',
'/','<tr><td>1</td><td>2</td></tr>') as upd1;
--disable_warnings
select updatexml('', '/', '') as upd2;
--enable_warnings
#
# Bug#16234 XML: Crash if ExtractValue()
@@ -707,7 +709,9 @@ SELECT UPDATEXML(CONVERT('' USING swe7), TRUNCATE('',1), 0);
--echo #
--echo # Bug#12375190: UPDATEXML CRASHES ON SIMPLE INPUTS
--echo #
--disable_warnings
SELECT UPDATEXML('','(a)/a','');
--enable_warnings
SELECT UPDATEXML('<a><a>x</a></a>','(a)/a','<b />');
SELECT UPDATEXML('<a><c><a>x</a></c></a>','(a)/a','<b />');
SELECT UPDATEXML('<a><c><a>x</a></c></a>','(a)//a','<b />');

View File

@@ -134,10 +134,10 @@ t1 CREATE TABLE `t1` (
`x` xmltype DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci
DROP TABLE t1;
CREATE FUNCTION xmi(id int) RETURNS xmltype RETURN 'aaa';
CREATE FUNCTION xmi(id int) RETURNS xmltype RETURN '<a></a>';
SELECT xmi(1);
xmi(1)
aaa
<a></a>
CREATE TABLE t1 SELECT xmi(10) as x;
SHOW CREATE TABLE t1;
Table Create Table
@@ -226,4 +226,29 @@ t1 CREATE TABLE `t1` (
`x` xmltype CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci
DROP TABLE t1;
CREATE TABLE t1(a XMLTYPE);
INSERT INTO t1 VALUES(1);
ERROR HY000: Cannot cast 'int' as 'xmltype' in assignment of `test`.`t1`.`a`
INSERT INTO t1 VALUES(11.2);
ERROR HY000: Cannot cast 'decimal' as 'xmltype' in assignment of `test`.`t1`.`a`
INSERT INTO t1 VALUES(11e-2);
ERROR HY000: Cannot cast 'double' as 'xmltype' in assignment of `test`.`t1`.`a`
INSERT INTO t1 VALUES("one");
ERROR 22007: Incorrect XML value: 'one'
INSERT INTO t1 VALUES("<a><b></b>");
ERROR 22007: Incorrect XML value: '<a><b></b>'
INSERT INTO t1 VALUES("<a><b></b></a>");
INSERT INTO t1 VALUES("<to>JOHN</to><from></from>");
ERROR 22007: Incorrect XML value: '<to>JOHN</to><from></from>'
INSERT INTO t1 VALUES('<note><to>JOHN</to><from></from></note>');
INSERT INTO t1 VALUES('<?xml version="1.0" ?> <note><to>JOHN</to><from></from></note>');
INSERT INTO t1 VALUES('<?xml version="1.0" ?> <?xml?> <note><to>JOHN</to><from></from></note>');
ERROR 22007: Incorrect XML value: '<?xml version="1.0" ?> <?xml?> <note><to>JOHN</to><from></from></note>'
INSERT INTO t1 VALUES('<note><to>JOHN</to><from></from></note> <?xml version="1.0" ?> ');
ERROR 22007: Incorrect XML value: '<note><to>JOHN</to><from></from></note> <?xml version="1.0" ?> '
INSERT INTO t1 VALUES('<?somePI pi content ?> <?xml version="1.0" ?> <note><from>Lisa</from></note>');
ERROR 22007: Incorrect XML value: '<?somePI pi content ?> <?xml version="1.0" ?> <note><from>Lisa</from></note>'
INSERT INTO t1 VALUES('<?somePI pi content ?> <note><from>Lisa</from></note>');
INSERT INTO t1 VALUES('<?xml version="1.0" ?> <note><?somePI pi content ?><from>Lisa</from></note>');
DROP TABLE t1;
# End of 11.8 tests

View File

@@ -81,7 +81,7 @@ ALTER TABLE t1 ADD COLUMN x xmltype;
SHOW CREATE TABLE t1;
DROP TABLE t1;
CREATE FUNCTION xmi(id int) RETURNS xmltype RETURN 'aaa';
CREATE FUNCTION xmi(id int) RETURNS xmltype RETURN '<a></a>';
SELECT xmi(1);
CREATE TABLE t1 SELECT xmi(10) as x;
@@ -155,5 +155,36 @@ CREATE TABLE t1 AS SELECT UpdateXML('<a><b>ccc</b><d></d></a>', '/a', '<e>fff</e
SHOW CREATE TABLE t1;
DROP TABLE t1;
CREATE TABLE t1(a XMLTYPE);
--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
INSERT INTO t1 VALUES(1);
--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
INSERT INTO t1 VALUES(11.2);
--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
INSERT INTO t1 VALUES(11e-2);
--error ER_TRUNCATED_WRONG_VALUE
INSERT INTO t1 VALUES("one");
--error ER_TRUNCATED_WRONG_VALUE
INSERT INTO t1 VALUES("<a><b></b>");
INSERT INTO t1 VALUES("<a><b></b></a>");
--error ER_TRUNCATED_WRONG_VALUE
INSERT INTO t1 VALUES("<to>JOHN</to><from></from>");
INSERT INTO t1 VALUES('<note><to>JOHN</to><from></from></note>');
INSERT INTO t1 VALUES('<?xml version="1.0" ?> <note><to>JOHN</to><from></from></note>');
--error ER_TRUNCATED_WRONG_VALUE
INSERT INTO t1 VALUES('<?xml version="1.0" ?> <?xml?> <note><to>JOHN</to><from></from></note>');
--error ER_TRUNCATED_WRONG_VALUE
INSERT INTO t1 VALUES('<note><to>JOHN</to><from></from></note> <?xml version="1.0" ?> ');
--error ER_TRUNCATED_WRONG_VALUE
INSERT INTO t1 VALUES('<?somePI pi content ?> <?xml version="1.0" ?> <note><from>Lisa</from></note>');
INSERT INTO t1 VALUES('<?somePI pi content ?> <note><from>Lisa</from></note>');
INSERT INTO t1 VALUES('<?xml version="1.0" ?> <note><?somePI pi content ?><from>Lisa</from></note>');
DROP TABLE t1;
--echo # End of 11.8 tests

View File

@@ -16,6 +16,7 @@
#define MYSQL_SERVER
#include "mariadb.h"
#include "my_xml.h"
#include "sql_class.h"
#include "sql_lex.h"
#include "sql_type_xmltype.h"
@@ -186,6 +187,36 @@ int Field_xmltype::report_wrong_value(const ErrConv &val)
}
static int check_parse_xml(const char *xml, size_t length, CHARSET_INFO *cs)
{
MY_XML_PARSER p;
/* Prepare XML parser */
my_xml_parser_create(&p);
p.flags= MY_XML_FLAG_RELATIVE_NAMES |
MY_XML_FLAG_SKIP_TEXT_NORMALIZATION |
MY_XML_FLAG_ASSERT_WELL_FORMED;
return my_xml_parse(&p, xml, length);
}
int Field_xmltype::store(const char *from, size_t length, CHARSET_INFO *cs)
{
if (length < 4 ||
check_parse_xml(from, length, cs) != MY_XML_OK)
goto err;
return Field_blob::store(from, length, cs);
err:
get_thd()->push_warning_wrong_value(
Sql_condition::WARN_LEVEL_WARN, "XML", from);
return -1;
}
class Item_xmltype_typecast_func_handler: public Item_handled_func::Handler_str
{
public:

View File

@@ -117,6 +117,8 @@ public:
void sql_type(String &str) const override;
uint size_of() const override { return sizeof(*this); }
int store(const char *to, size_t length, CHARSET_INFO *charset) override;
using Field_str::store;
};

View File

@@ -30,9 +30,9 @@
#define MY_XML_SLASH '/'
#define MY_XML_COMMENT 'C'
#define MY_XML_TEXT 'T'
#define MY_XML_QUESTION '?'
#define MY_XML_EXCLAM '!'
#define MY_XML_CDATA 'D'
#define MY_XML_PROCESSING_INSTRUCTION '?'
#define MY_XML_CDATA 'A'
#define MY_XML_DTD 'D'
typedef struct xml_attr_st
{
@@ -95,8 +95,8 @@ static const char *lex2str(int lex)
case MY_XML_SLASH: return "'/'";
case MY_XML_COMMENT: return "COMMENT";
case MY_XML_TEXT: return "TEXT";
case MY_XML_QUESTION: return "'?'";
case MY_XML_EXCLAM: return "'!'";
case MY_XML_PROCESSING_INSTRUCTION: return "'?'";
case MY_XML_DTD: return "DTD";
}
return "unknown token";
}
@@ -145,6 +145,43 @@ static int my_xml_scan(MY_XML_PARSER *p,MY_XML_ATTR *a)
a->end=p->cur;
lex=MY_XML_COMMENT;
}
else if (!my_xml_parser_prefix_cmp(p, C_STRING_WITH_LEN("<?")))
{
lex= MY_XML_UNKNOWN;
p->cur+= 2;
a->beg= p->cur;
if (my_xml_is_id0(p->cur[0]))
{
int termination_expected= FALSE;
p->cur++;
while (p->cur < p->end && my_xml_is_id1(p->cur[0]))
p->cur++;
a->end=p->cur;
/*
After the target name the syntax requires the
terminating '?>' or some spaces then an
arbitrary string to the '>?'.
*/
if (p->cur < p->end && !my_xml_is_space(p->cur[0]))
termination_expected= TRUE;
while (p->cur < p->end)
{
if (my_xml_parser_prefix_cmp(p, C_STRING_WITH_LEN("?>")) == 0)
{
lex= MY_XML_PROCESSING_INSTRUCTION;
p->cur+= 2;
break;
}
else if (termination_expected)
break;
p->cur++;
}
}
}
else if (!my_xml_parser_prefix_cmp(p, C_STRING_WITH_LEN("<![CDATA[")))
{
p->cur+= 9;
@@ -159,7 +196,56 @@ static int my_xml_scan(MY_XML_PARSER *p,MY_XML_ATTR *a)
}
lex= MY_XML_CDATA;
}
else if (strchr("?=/<>!",p->cur[0]))
else if (!my_xml_parser_prefix_cmp(p, C_STRING_WITH_LEN("<!DOCTYPE")))
{
/* skip <!DOCTYPE part. TODO: add parser for the DTD */
int in_quote= 0;
int level= 1;
p->cur+= 9;
lex= MY_XML_UNKNOWN;
for (; p->cur < p->end; p->cur++)
{
if (p->cur[0] == '"' || p->cur[0] == '\'')
{
if (!in_quote)
in_quote= p->cur[0];
else
{
if (p->cur[0] == in_quote)
in_quote= 0;
}
}
if (in_quote)
continue;
switch (p->cur[0])
{
case MY_XML_LT:
{
level++;
break;
}
case MY_XML_GT:
{
level--;
if (level == 0)
{
lex= MY_XML_DTD;
p->cur++;
goto dtd_end;
}
}
default:;
}
}
dtd_end:
a->end= p->cur;
}
else if (strchr("=/<>",p->cur[0]))
{
p->cur++;
a->end=p->cur;
@@ -205,6 +291,20 @@ ret:
static int my_xml_value(MY_XML_PARSER *st, const char *str, size_t len)
{
if ((st->flags & MY_XML_FLAG_ASSERT_WELL_FORMED) &&
st->attr.end == st->attr.start)
{
/*
Values are not allowed on the root level of XML.
Though spaces are ok.
*/
while (len > 0)
{
if (!my_xml_is_space(str[--len]))
return MY_XML_ERROR;
}
return MY_XML_OK;
}
return (st->value) ? (st->value)(st,str,len) : MY_XML_OK;
}
@@ -266,6 +366,14 @@ static int my_xml_enter(MY_XML_PARSER *st, const char *str, size_t len)
st->attr.end[0]= '/';
st->attr.end++;
}
else if ((st->flags & MY_XML_FLAG_ASSERT_WELL_FORMED) &&
st->current_node_type == MY_XML_NODE_TAG)
{
/* level 0 nesting of the XML document .*/
if (st->root_node_counter++)
return MY_XML_ERROR; /* no multiple root nodes allowed. */
st->item_counter++;
}
memcpy(st->attr.end, str, len);
st->attr.end+= len;
st->attr.end[0]= '\0';
@@ -340,19 +448,46 @@ int my_xml_parse(MY_XML_PARSER *p,const char *str, size_t len)
if (p->cur[0] == '<')
{
int lex;
int question=0;
int exclam=0;
lex=my_xml_scan(p,&a);
if (MY_XML_COMMENT == lex)
if (MY_XML_COMMENT == lex ||
MY_XML_DTD == lex)
{
p->item_counter++;
continue;
}
if (MY_XML_PROCESSING_INSTRUCTION == lex)
{
size_t tag_len= (size_t) (a.end - a.beg);
if ((p->flags & MY_XML_FLAG_ASSERT_WELL_FORMED) &&
tag_len == 3 && memcmp(a.beg, "xml", 3) == 0)
{
/*
The <?xml ..?> processing instruction has special meaning.
If appears it must be first line of the XML document.
*/
if (p->item_counter)
return MY_XML_ERROR;
}
p->item_counter++;
if (p->processing_instruction &&
MY_XML_OK != p->processing_instruction(p,
a.beg, (size_t) (a.end-a.beg),
a.end, (size_t) (p->cur - a.end - 2)))
return MY_XML_ERROR;
continue;
}
if (lex == MY_XML_CDATA)
{
a.beg+= 9;
a.end-= 3;
my_xml_value(p, a.beg, (size_t) (a.end-a.beg));
if (MY_XML_OK != my_xml_value(p, a.beg, (size_t) (a.end-a.beg)))
return MY_XML_ERROR;
continue;
}
@@ -371,16 +506,6 @@ int my_xml_parse(MY_XML_PARSER *p,const char *str, size_t len)
goto gt;
}
if (MY_XML_EXCLAM == lex)
{
lex=my_xml_scan(p,&a);
exclam=1;
}
else if (MY_XML_QUESTION == lex)
{
lex=my_xml_scan(p,&a);
question=1;
}
if (MY_XML_IDENT == lex)
{
@@ -395,8 +520,7 @@ int my_xml_parse(MY_XML_PARSER *p,const char *str, size_t len)
return MY_XML_ERROR;
}
while ((MY_XML_IDENT == (lex=my_xml_scan(p,&a))) ||
((MY_XML_STRING == lex && exclam)))
while (MY_XML_IDENT == (lex=my_xml_scan(p,&a)))
{
MY_XML_ATTR b;
if (MY_XML_EQ == (lex=my_xml_scan(p,&b)))
@@ -424,15 +548,6 @@ int my_xml_parse(MY_XML_PARSER *p,const char *str, size_t len)
(MY_XML_OK != my_xml_leave(p,a.beg,(size_t) (a.end-a.beg))))
return MY_XML_ERROR;
}
else if ((MY_XML_STRING == lex) && exclam)
{
/*
We are in <!DOCTYPE>, e.g.
<!DOCTYPE name SYSTEM "SystemLiteral">
<!DOCTYPE name PUBLIC "PublicLiteral" "SystemLiteral">
Just skip "SystemLiteral" and "PublicidLiteral"
*/
}
else
break;
}
@@ -445,24 +560,6 @@ int my_xml_parse(MY_XML_PARSER *p,const char *str, size_t len)
}
gt:
if (question)
{
if (lex != MY_XML_QUESTION)
{
snprintf(p->errstr,sizeof(p->errstr),"%s unexpected ('?' wanted)",lex2str(lex));
return MY_XML_ERROR;
}
if (MY_XML_OK != my_xml_leave(p,NULL,0))
return MY_XML_ERROR;
lex=my_xml_scan(p,&a);
}
if (exclam)
{
if (MY_XML_OK != my_xml_leave(p,NULL,0))
return MY_XML_ERROR;
}
if (lex != MY_XML_GT)
{
snprintf(p->errstr,sizeof(p->errstr),"%s unexpected ('>' wanted)",lex2str(lex));
@@ -479,12 +576,15 @@ gt:
my_xml_norm_text(&a);
if (a.beg != a.end)
{
my_xml_value(p,a.beg,(size_t) (a.end-a.beg));
if (MY_XML_OK != my_xml_value(p, a.beg, (size_t) (a.end-a.beg)))
return MY_XML_ERROR;
}
}
}
if (p->attr.start[0])
if (p->attr.start[0] ||
((p->flags & MY_XML_FLAG_ASSERT_WELL_FORMED) &&
p->root_node_counter == 0))
{
snprintf(p->errstr,sizeof(p->errstr),"unexpected END-OF-INPUT");
return MY_XML_ERROR;
@@ -537,6 +637,14 @@ void my_xml_set_leave_handler(MY_XML_PARSER *p,
}
void my_xml_set_processing_instruction_handler(MY_XML_PARSER *p,
int (*p_i)(MY_XML_PARSER *, const char *, size_t,
const char *, size_t))
{
p->processing_instruction= p_i;
}
void my_xml_set_user_data(MY_XML_PARSER *p, void *user_data)
{
p->user_data=user_data;