Xml_node: safe alternatives to unsafe accessors

Issue #3125
This commit is contained in:
Norman Feske 2019-01-21 10:33:23 +01:00
parent 8cc11d6cc6
commit 5f1f67153b
3 changed files with 210 additions and 135 deletions

View File

@ -116,32 +116,61 @@ class Genode::Xml_attribute
/**
* Return size of value
*
* \deprecated use 'with_raw_node' instead
*/
size_t value_size() const { return _value.len() - 2; }
char const *value_base() const { return _value.start() + 1; }
/**
* Return attribute value as null-terminated string
* Return size of the value in bytes
*/
void value(char *dst, size_t max_len) const
size_t value_size() const
{
/*
* The value of 'max_len' denotes the maximum number of
* characters to be written to 'dst' including the null
* termination. From the quoted value string, we strip
* both quote characters and add a null-termination
* character.
* The size of the actual value content excludes both the starting
* and the trailing quote character.
*
* The invariant 'len >= 2' is enforced by the 'Xml_attribute'
* constructor by checking the '_value' type for being a 'STRING'.
*/
max_len = min(max_len, _value.len() - 2 + 1);
strncpy(dst, _value.start() + 1, max_len);
return _value.len() - 2;
}
/**
* Return true if attribute has the specified value
*/
bool has_value(const char *value) const {
return strlen(value) == (_value.len() - 2) &&
!strcmp(value, _value.start() + 1, _value.len() - 2); }
return strlen(value) == (_value.len() - 2)
&& !strcmp(value, _value.start() + 1, _value.len() - 2); }
/**
* Call functor 'fn' with the data of the attribute value as argument
*
* The functor is called with the start pointer ('char const *') and
* size (size_t) of the attribute value as arguments.
*
* Note that the content of the buffer is not null-terminated but
* delimited by the size argument.
*/
template <typename FN>
void with_raw_value(FN const &fn) const
{
/*
* Skip leading quote of the '_value' to access the actual value.
*/
fn(_value.start() + 1, value_size());
}
/**
* Return attribute value as null-terminated string
*
* \deprecated
*/
void value(char *dst, size_t max_len) const
{
with_raw_value([&] (char const *start, size_t length) {
Genode::strncpy(dst, start, min(max_len, length + 1)); });
}
/**
* Return attribute value as typed value
@ -154,13 +183,34 @@ class Genode::Xml_attribute
template <typename T>
bool value(T &out) const
{
/*
* The '_value' token starts with a quote, which we
* need to skip to access the string. For validating
* the length, we have to consider both the starting
* and the trailing quote character.
*/
return ascii_to(_value.start() + 1, out) == _value.len() - 2;
bool result = false;
with_raw_value([&] (char const *start, size_t length) {
result = (ascii_to(start, out) == length); });
return result;
}
/**
* Return attribute value as 'Genode::String'
*/
template <size_t N>
void value(String<N> &out) const
{
with_raw_value([&] (char const *start, size_t length) {
out = String<N>(Cstring(start, length)); });
}
/**
* Return attribute value as 'Genode::String'
*
* \deprecated use 'value(String<N> &out' instead
*/
template <size_t N>
void value(String<N> *out) const
{
with_raw_value([&] (char const *start, size_t length) {
*out = String<N>(Cstring(start, length)); });
}
/**
@ -169,17 +219,14 @@ class Genode::Xml_attribute
* \deprecated use 'value(T &out)' instead
*/
template <typename T>
bool value(T *out) const { return value(*out); }
/**
* Return attribute value as Genode::String
*/
template <size_t N>
void value(String<N> *out) const
bool value(T *out) const
{
char buf[N];
value(buf, sizeof(buf));
*out = String<N>(Cstring(buf));
bool result = false;
with_raw_value([&] (char const *start, size_t length) {
result = (ascii_to(start, *out) == length); });
return result;
}
/**
@ -451,36 +498,34 @@ class Genode::Xml_node
}
};
const char *_addr; /* first character of XML data */
size_t _max_len; /* length of XML data in characters */
int _num_sub_nodes; /* number of immediate sub nodes */
Tag _start_tag;
Tag _end_tag;
int _num_sub_nodes { 0 }; /* number of immediate sub nodes */
const char * _addr; /* first character of XML data */
size_t _max_len; /* length of XML data in characters */
Tag _start_tag;
Tag _end_tag;
/**
* Search for end tag of XML node and initialize '_num_sub_nodes'
* Search matching end tag for given start tag and detemine number of
* immediate sub nodes along the way.
*
* \return end tag or invalid tag
*
* The method searches for a end tag that matches the same
* depth level and the same name as the start tag of the XML
* node. If the XML structure is invalid, the search results
* is an invalid Tag.
*
* During the search, the method also counts the number of
* immediate sub nodes.
* The method searches for a end tag that matches the same depth level
* and the same name as the start tag of the XML node. If the XML
* structure is invalid, the search results is an invalid Tag.
*/
Tag _init_end_tag()
static Tag _search_end_tag(Tag start_tag, int &sub_nodes_count)
{
/*
* If the start tag is invalid or an empty-element tag,
* we use the same tag as end tag.
*/
if (_start_tag.type() != Tag::START)
return _start_tag;
if (start_tag.type() != Tag::START)
return start_tag;
int depth = 1;
Token curr_token = _start_tag.next_token();
Token curr_token = start_tag.next_token();
while (curr_token.type() != Token::END) {
@ -500,7 +545,7 @@ class Genode::Xml_node
/* count sub nodes at depth 1 */
if (depth == 1 && curr_tag.node())
_num_sub_nodes++;
sub_nodes_count++;
/* keep track of the current depth */
depth += (curr_tag.type() == Tag::START);
@ -515,10 +560,10 @@ class Genode::Xml_node
}
/* reaching the same depth as the start tag */
const char *start_name = _start_tag.name().start();
size_t start_len = _start_tag.name().len();
const char *curr_name = curr_tag.name().start();
size_t curr_len = curr_tag.name().len();
const char *start_name = start_tag.name().start();
size_t start_len = start_tag.name().len();
const char *curr_name = curr_tag.name().start();
size_t curr_len = curr_tag.name().len();
/* on mismatch of start tag and end tag, return invalid tag */
if (start_len != curr_len
@ -567,12 +612,17 @@ class Genode::Xml_node
*/
Xml_node _sub_node(const char *at) const
{
if (at < addr() || (size_t)(at - addr()) >= _max_len)
if (at < _addr || (size_t)(at - _addr) >= _max_len)
throw Nonexistent_sub_node();
return Xml_node(at, _max_len - (at - addr()));
return Xml_node(at, _max_len - (at - _addr));
}
/**
* Return pointer to start of content
*/
char const *_content_base() const { return _start_tag.next_token().start(); }
public:
/**
@ -583,12 +633,12 @@ class Genode::Xml_node
*
* \throw Invalid_syntax
*/
Xml_node(const char *addr, size_t max_len = ~0UL) :
Xml_node(const char *addr, size_t max_len = ~0UL)
:
_addr(addr),
_max_len(max_len),
_num_sub_nodes(0),
_start_tag(skip_non_tag_characters(Token(addr, max_len))),
_end_tag(_init_end_tag())
_end_tag(_search_end_tag(_start_tag, _num_sub_nodes))
{
/* check validity of XML node */
if (_start_tag.type() == Tag::EMPTY) return;
@ -603,6 +653,32 @@ class Genode::Xml_node
void type_name(char *dst, size_t max_len) const {
_start_tag.name().string(dst, max_len); }
/**
* Return size of node including start and end tags in bytes
*/
size_t size() const { return _end_tag.next_token().start() - _addr; }
/**
* Return pointer to start of node
*
* \deprecated use 'with_raw_node' instead
*/
char const *addr() const { return _addr; }
/**
* Return size of node content
*/
size_t content_size() const
{
if (_start_tag.type() == Tag::EMPTY)
return 0;
return _end_tag.token().start() - _content_base();
}
/**
* Request type name of XML node as null-terminated string
*/
typedef String<64> Type;
Type type() const
{
@ -619,48 +695,35 @@ class Genode::Xml_node
&& strlen(type) == _start_tag.name().len()); }
/**
* Request content of XML node as null-terminated string
* Call functor 'fn' with the node data '(char const *, size_t)'
*/
void value(char *dst, size_t max_len) const {
max_len = min(content_size() + 1, min(max_len, _max_len));
strncpy(dst, content_addr(), max_len); }
template <typename FN>
void with_raw_node(FN const &fn) const
{
fn(_addr, _end_tag.next_token().start() - _addr);
}
/**
* Read content as typed value from XML node
* Call functor 'fn' with content '(char const *, size_t) as argument'
*
* \param T type of value to read from XML node
* \param out resulting value
* \return true on success
*/
template <typename T>
bool value(T &out) const {
return ascii_to(content_addr(), out) == content_size(); }
/**
* Read content as typed value from XML node
* Note that the content is not null-terminated. It points directly
* into a sub range of the unmodified 'Xml_node' data.
*
* \deprecated use 'value(T &out)' instead
* If the node has no content, the functor 'fn' is not called.
*/
template <typename T> bool value(T *out) const { return value(*out); }
template <typename FN>
void with_raw_content(FN const &fn) const
{
if (_start_tag.type() == Tag::EMPTY)
return;
fn(_content_base(), content_size());
}
/**
* Return begin of node including the start tag
*/
const char *addr() const { return _addr; }
/**
* Return size of node including start and end tags
*/
size_t size() const { return _end_tag.next_token().start() - addr(); }
/**
* Return begin of node content as an opaque string
* Return pointer to start of content
*
* Note that the returned string is not null-terminated as it
* points directly into a sub range of the unmodified Xml_node
* address range.
*
* XXX This method is deprecated. Use 'content_base()' instead.
* XXX This method is deprecated. Use 'with_raw_content()' instead.
*
* \noapi
*/
@ -668,19 +731,19 @@ class Genode::Xml_node
/**
* Return pointer to start of content
*
* XXX This method is deprecated. Use 'with_raw_content()' instead.
*
* \noapi
*/
char const *content_base() const { return content_addr(); }
/**
* Return size of node content
* Return content as out value
*
* \deprecated use with_raw_content instead
*/
size_t content_size() const
{
if (_start_tag.type() == Tag::EMPTY)
return 0;
return _end_tag.token().start() - content_addr();
}
template <typename T> bool value(T *out) const { return value(*out); }
/**
* Export decoded node content from XML node
@ -695,8 +758,8 @@ class Genode::Xml_node
size_t decoded_content(char *dst, size_t dst_len) const
{
size_t result_len = 0;
char const *src = content_base();
size_t src_len = content_size();
char const *src = _content_base();
size_t src_len = content_size();
for (; dst_len && src_len; dst_len--, result_len++) {
@ -744,8 +807,7 @@ class Genode::Xml_node
/**
* Return next XML node of specified type
*
* \param type type of XML node, or
* 0 for matching any type
* \param type type of XML node, or nullptr for matching any type
*/
Xml_node next(const char *type) const
{
@ -776,7 +838,7 @@ class Genode::Xml_node
/* look up node at specified index */
try {
Xml_node curr_node = _sub_node(content_addr());
Xml_node curr_node = _sub_node(_content_base());
for (; idx > 0; idx--)
curr_node = curr_node.next();
return curr_node;
@ -797,7 +859,7 @@ class Genode::Xml_node
/* search for sub node of specified type */
try {
Xml_node curr_node = _sub_node(content_addr());
Xml_node curr_node = _sub_node(_content_base());
for ( ; true; curr_node = curr_node.next())
if (curr_node.has_type(type))
return curr_node;
@ -807,6 +869,19 @@ class Genode::Xml_node
throw Nonexistent_sub_node();
}
/**
* Apply functor 'fn' to first sub node of specified type
*
* The functor is called with the sub node as argument.
* If no matching sub node exists, the functor is not called.
*/
template <typename FN>
void with_sub_node(char const *type, FN const &fn) const
{
if (has_sub_node(type))
fn(sub_node(type));
}
/**
* Execute functor 'fn' for each sub node of specified type
*/
@ -872,27 +947,21 @@ class Genode::Xml_node
}
/**
* Shortcut for reading an attribute value from XML node
* Read attribute value from XML node
*
* \param type attribute name
* \param default_value value returned if no attribute with the
* name 'type' is present.
* \return attribute value or specified default value
*
* Without this shortcut, attribute values can be obtained by
* 'node.attribute(...).value(...)' only. Because the attribute
* lookup may throw a 'Nonexistent_attribute' exception, code that
* reads optional attributes (those with default values) has to
* handle the exception accordingly. Such code tends to become
* clumsy, in particular when many attributes are processed in a
* subsequent fashion. This method template relieves the XML node
* user from implementing the exception handling manually.
* The type of the return value corresponds to the type of the
* default value.
*/
template <typename T>
inline T attribute_value(char const *type, T default_value) const
inline T attribute_value(char const *type, T const default_value) const
{
T result = default_value;
try { attribute(type).value(&result); } catch (...) { }
try { attribute(type).value(result); } catch (...) { }
return result;
}
@ -915,7 +984,16 @@ class Genode::Xml_node
}
void print(Output &output) const {
output.out_string(addr(), size()); }
output.out_string(_addr, size()); }
/**
* Return true if this node differs from 'another'
*/
bool differs_from(Xml_node const &another) const
{
return size() != another.size() ||
memcmp(_addr, another._addr, size()) != 0;
}
};
#endif /* _INCLUDE__UTIL__XML_NODE_H_ */

View File

@ -80,21 +80,21 @@
[init -> test-xml_node] XML node: name = "program", number of subnodes = 2
[init -> test-xml_node] XML node: name = "filename", leaf content = "init"
[init -> test-xml_node] XML node: name = "quota", leaf content = "16M"
[init -> test-xml_node] XML node: name = "single-tag", leaf content = ""
[init -> test-xml_node] XML node: name = "single-tag-with-attr", leaf content = ""
[init -> test-xml_node] XML node: name = "single-tag"
[init -> test-xml_node] XML node: name = "single-tag-with-attr"
[init -> test-xml_node] attribute name="name", value="ein_name"
[init -> test-xml_node] attribute name="quantum", value="2K"
[init -> test-xml_node]
[init -> test-xml_node] -- Test parsing XML with nodes mixed with text --
[init -> test-xml_node] XML node: name = "config", number of subnodes = 2
[init -> test-xml_node] XML node: name = "program", leaf content = ""
[init -> test-xml_node] XML node: name = "program"
[init -> test-xml_node] attribute name="attr", value="abcd"
[init -> test-xml_node] XML node: name = "program", leaf content = "inProgram"
[init -> test-xml_node]
[init -> test-xml_node] -- Test parsing XML with comments --
[init -> test-xml_node] XML node: name = "config", number of subnodes = 2
[init -> test-xml_node] XML node: name = "visible-tag", leaf content = ""
[init -> test-xml_node] XML node: name = "visible-tag", leaf content = ""
[init -> test-xml_node] XML node: name = "visible-tag"
[init -> test-xml_node] XML node: name = "visible-tag"
[init -> test-xml_node]
[init -> test-xml_node] -- Test exporting decoded content from XML node --
[init -> test-xml_node] step 1

View File

@ -222,12 +222,10 @@ struct Formatted_xml_attribute
void print(Output &output) const
{
char value[32]; value[0] = 0;
_attr.value(value, sizeof(value));
Genode::print(output, Indentation(_indent),
"attribute name=\"", _attr.name(), "\", "
"value=\"", Cstring(value), "\"");
_attr.with_raw_value([&] (char const *start, size_t length) {
Genode::print(output, Indentation(_indent),
"attribute name=\"", _attr.name(), "\", "
"value=\"", Cstring(start, length), "\""); });
}
};
@ -265,13 +263,13 @@ struct Formatted_xml_node
/* print node information */
print(output, Indentation(_indent),
"XML node: name = \"", _node.type(), "\", ");
"XML node: name = \"", _node.type(), "\"");
if (_node.num_sub_nodes() == 0) {
char buf[128];
_node.value(buf, sizeof(buf));
print(output, "leaf content = \"", Cstring(buf), "\"");
} else
print(output, "number of subnodes = ", _node.num_sub_nodes());
_node.with_raw_content([&] (char const *start, size_t length) {
print(output, ", leaf content = \"", Cstring(start, length), "\""); });
} else {
print(output, ", number of subnodes = ", _node.num_sub_nodes());
}
print(output, "\n");
@ -296,9 +294,8 @@ static void log_key(Xml_node node, char const *key)
{
try {
Xml_node sub_node = node.sub_node(key);
char buf[32];
sub_node.value(buf, sizeof(buf));
log("content of sub node \"", key, "\" = \"", Cstring(buf), "\"");
sub_node.with_raw_content([&] (char const *start, size_t length) {
log("content of sub node \"", key, "\" = \"", Cstring(start, length), "\""); });
} catch (Xml_node::Nonexistent_sub_node) {
log("sub node \"", key, "\" is not defined\n");
} catch (Xml_node::Invalid_syntax) {