From 4651afdd457eca06da07331186bf28b98df2eeff Mon Sep 17 00:00:00 2001 From: Andrej Valek Date: Wed, 14 Jun 2017 14:38:35 +0200 Subject: libxml2: Avoid reparsing and simplify control flow in xmlParseStartTag2 Signed-off-by: Andrej Valek --- ...ibxml2-fix_and_simplify_xmlParseStartTag2.patch | 590 +++++++++++++++++++++ meta/recipes-core/libxml/libxml2_2.9.4.bb | 1 + 2 files changed, 591 insertions(+) create mode 100644 meta/recipes-core/libxml/libxml2/libxml2-fix_and_simplify_xmlParseStartTag2.patch (limited to 'meta/recipes-core/libxml') diff --git a/meta/recipes-core/libxml/libxml2/libxml2-fix_and_simplify_xmlParseStartTag2.patch b/meta/recipes-core/libxml/libxml2/libxml2-fix_and_simplify_xmlParseStartTag2.patch new file mode 100644 index 0000000000..faa57701f5 --- /dev/null +++ b/meta/recipes-core/libxml/libxml2/libxml2-fix_and_simplify_xmlParseStartTag2.patch @@ -0,0 +1,590 @@ +libxml2-2.9.4: Avoid reparsing and simplify control flow in xmlParseStartTag2 + +[No upstream tracking] + +parser: Avoid reparsing in xmlParseStartTag2 + +The code in xmlParseStartTag2 must handle the case that the input +buffer was grown and reallocated which can invalidate pointers to +attribute values. Before, this was handled by detecting changes of +the input buffer "base" pointer and, in case of a change, jumping +back to the beginning of the function and reparsing the start tag. + +The major problem of this approach is that whether an input buffer is +reallocated is nondeterministic, resulting in seemingly random test +failures. See the mailing list thread "runtest mystery bug: name2.xml +error case regression test" from 2012, for example. + +If a reallocation was detected, the code also made no attempts to +continue parsing in case of errors which makes a difference in +the lax "recover" mode. + +Now we store the current input buffer "base" pointer for each (not +separately allocated) attribute in the namespace URI field, which isn't +used until later. After the whole start tag was parsed, the pointers to +the attribute values are reconstructed using the offset between the +new and the old input buffer. This relies on arithmetic on dangling +pointers which is technically undefined behavior. But it seems like +the easiest and most efficient fix and a similar approach is used in +xmlParserInputGrow. + +This changes the error output of several tests, typically making it +more verbose because we try harder to continue parsing in case of errors. + +(Another possible solution is to check not only the "base" pointer +but the size of the input buffer as well. But this would result in +even more reparsing.) + +Remove some goto labels and deduplicate a bit of code after handling +namespaces. + +There were two bugs where parameter-entity references could lead to an +unexpected change of the input buffer in xmlParseNameComplex and +xmlDictLookup being called with an invalid pointer. + + +Upstream-Status: Backport + - [https://git.gnome.org/browse/libxml2/commit/?id=07b7428b69c368611d215a140fe630b2d1e61349] + - [https://git.gnome.org/browse/libxml2/commit/?id=855c19efb7cd30d927d673b3658563c4959ca6f0] +Signed-off-by: Andrej Valek + +diff --git a/parser.c b/parser.c +index 609a270..74016e3 100644 +--- a/parser.c ++++ b/parser.c +@@ -43,6 +43,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -9377,8 +9378,7 @@ xmlParseStartTag2(xmlParserCtxtPtr ctxt, const xmlChar **pref, + const xmlChar **atts = ctxt->atts; + int maxatts = ctxt->maxatts; + int nratts, nbatts, nbdef; +- int i, j, nbNs, attval, oldline, oldcol, inputNr; +- const xmlChar *base; ++ int i, j, nbNs, attval; + unsigned long cur; + int nsNr = ctxt->nsNr; + +@@ -9392,13 +9392,8 @@ xmlParseStartTag2(xmlParserCtxtPtr ctxt, const xmlChar **pref, + * The Shrinking is only possible once the full set of attribute + * callbacks have been done. + */ +-reparse: + SHRINK; +- base = ctxt->input->base; + cur = ctxt->input->cur - ctxt->input->base; +- inputNr = ctxt->inputNr; +- oldline = ctxt->input->line; +- oldcol = ctxt->input->col; + nbatts = 0; + nratts = 0; + nbdef = 0; +@@ -9422,8 +9417,6 @@ reparse: + */ + SKIP_BLANKS; + GROW; +- if ((ctxt->input->base != base) || (inputNr != ctxt->inputNr)) +- goto base_changed; + + while (((RAW != '>') && + ((RAW != '/') || (NXT(1) != '>')) && +@@ -9434,203 +9427,174 @@ reparse: + + attname = xmlParseAttribute2(ctxt, prefix, localname, + &aprefix, &attvalue, &len, &alloc); +- if ((ctxt->input->base != base) || (inputNr != ctxt->inputNr)) { +- if ((attvalue != NULL) && (alloc != 0)) +- xmlFree(attvalue); +- attvalue = NULL; +- goto base_changed; +- } +- if ((attname != NULL) && (attvalue != NULL)) { +- if (len < 0) len = xmlStrlen(attvalue); +- if ((attname == ctxt->str_xmlns) && (aprefix == NULL)) { +- const xmlChar *URL = xmlDictLookup(ctxt->dict, attvalue, len); +- xmlURIPtr uri; +- +- if (URL == NULL) { +- xmlErrMemory(ctxt, "dictionary allocation failure"); +- if ((attvalue != NULL) && (alloc != 0)) +- xmlFree(attvalue); +- return(NULL); +- } +- if (*URL != 0) { +- uri = xmlParseURI((const char *) URL); +- if (uri == NULL) { +- xmlNsErr(ctxt, XML_WAR_NS_URI, +- "xmlns: '%s' is not a valid URI\n", +- URL, NULL, NULL); +- } else { +- if (uri->scheme == NULL) { +- xmlNsWarn(ctxt, XML_WAR_NS_URI_RELATIVE, +- "xmlns: URI %s is not absolute\n", +- URL, NULL, NULL); +- } +- xmlFreeURI(uri); +- } +- if (URL == ctxt->str_xml_ns) { +- if (attname != ctxt->str_xml) { +- xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, +- "xml namespace URI cannot be the default namespace\n", +- NULL, NULL, NULL); +- } +- goto skip_default_ns; +- } +- if ((len == 29) && +- (xmlStrEqual(URL, +- BAD_CAST "http://www.w3.org/2000/xmlns/"))) { +- xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, +- "reuse of the xmlns namespace name is forbidden\n", +- NULL, NULL, NULL); +- goto skip_default_ns; +- } +- } +- /* +- * check that it's not a defined namespace +- */ +- for (j = 1;j <= nbNs;j++) +- if (ctxt->nsTab[ctxt->nsNr - 2 * j] == NULL) +- break; +- if (j <= nbNs) +- xmlErrAttributeDup(ctxt, NULL, attname); +- else +- if (nsPush(ctxt, NULL, URL) > 0) nbNs++; +-skip_default_ns: +- if ((attvalue != NULL) && (alloc != 0)) { +- xmlFree(attvalue); +- attvalue = NULL; +- } +- if ((RAW == '>') || (((RAW == '/') && (NXT(1) == '>')))) +- break; +- if (!IS_BLANK_CH(RAW)) { +- xmlFatalErrMsg(ctxt, XML_ERR_SPACE_REQUIRED, +- "attributes construct error\n"); +- break; +- } +- SKIP_BLANKS; +- if ((ctxt->input->base != base) || (inputNr != ctxt->inputNr)) +- goto base_changed; +- continue; +- } +- if (aprefix == ctxt->str_xmlns) { +- const xmlChar *URL = xmlDictLookup(ctxt->dict, attvalue, len); +- xmlURIPtr uri; +- +- if (attname == ctxt->str_xml) { +- if (URL != ctxt->str_xml_ns) { +- xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, +- "xml namespace prefix mapped to wrong URI\n", +- NULL, NULL, NULL); +- } +- /* +- * Do not keep a namespace definition node +- */ +- goto skip_ns; +- } ++ if ((attname == NULL) || (attvalue == NULL)) ++ goto next_attr; ++ if (len < 0) len = xmlStrlen(attvalue); ++ ++ if ((attname == ctxt->str_xmlns) && (aprefix == NULL)) { ++ const xmlChar *URL = xmlDictLookup(ctxt->dict, attvalue, len); ++ xmlURIPtr uri; ++ ++ if (URL == NULL) { ++ xmlErrMemory(ctxt, "dictionary allocation failure"); ++ if ((attvalue != NULL) && (alloc != 0)) ++ xmlFree(attvalue); ++ return(NULL); ++ } ++ if (*URL != 0) { ++ uri = xmlParseURI((const char *) URL); ++ if (uri == NULL) { ++ xmlNsErr(ctxt, XML_WAR_NS_URI, ++ "xmlns: '%s' is not a valid URI\n", ++ URL, NULL, NULL); ++ } else { ++ if (uri->scheme == NULL) { ++ xmlNsWarn(ctxt, XML_WAR_NS_URI_RELATIVE, ++ "xmlns: URI %s is not absolute\n", ++ URL, NULL, NULL); ++ } ++ xmlFreeURI(uri); ++ } + if (URL == ctxt->str_xml_ns) { +- if (attname != ctxt->str_xml) { +- xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, +- "xml namespace URI mapped to wrong prefix\n", +- NULL, NULL, NULL); +- } +- goto skip_ns; +- } +- if (attname == ctxt->str_xmlns) { +- xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, +- "redefinition of the xmlns prefix is forbidden\n", +- NULL, NULL, NULL); +- goto skip_ns; +- } +- if ((len == 29) && +- (xmlStrEqual(URL, +- BAD_CAST "http://www.w3.org/2000/xmlns/"))) { +- xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, +- "reuse of the xmlns namespace name is forbidden\n", +- NULL, NULL, NULL); +- goto skip_ns; +- } +- if ((URL == NULL) || (URL[0] == 0)) { +- xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, +- "xmlns:%s: Empty XML namespace is not allowed\n", +- attname, NULL, NULL); +- goto skip_ns; +- } else { +- uri = xmlParseURI((const char *) URL); +- if (uri == NULL) { +- xmlNsErr(ctxt, XML_WAR_NS_URI, +- "xmlns:%s: '%s' is not a valid URI\n", +- attname, URL, NULL); +- } else { +- if ((ctxt->pedantic) && (uri->scheme == NULL)) { +- xmlNsWarn(ctxt, XML_WAR_NS_URI_RELATIVE, +- "xmlns:%s: URI %s is not absolute\n", +- attname, URL, NULL); +- } +- xmlFreeURI(uri); +- } +- } +- +- /* +- * check that it's not a defined namespace +- */ +- for (j = 1;j <= nbNs;j++) +- if (ctxt->nsTab[ctxt->nsNr - 2 * j] == attname) +- break; +- if (j <= nbNs) +- xmlErrAttributeDup(ctxt, aprefix, attname); +- else +- if (nsPush(ctxt, attname, URL) > 0) nbNs++; +-skip_ns: +- if ((attvalue != NULL) && (alloc != 0)) { +- xmlFree(attvalue); +- attvalue = NULL; +- } +- if ((RAW == '>') || (((RAW == '/') && (NXT(1) == '>')))) +- break; +- if (!IS_BLANK_CH(RAW)) { +- xmlFatalErrMsg(ctxt, XML_ERR_SPACE_REQUIRED, +- "attributes construct error\n"); +- break; +- } +- SKIP_BLANKS; +- if ((ctxt->input->base != base) || (inputNr != ctxt->inputNr)) +- goto base_changed; +- continue; +- } ++ if (attname != ctxt->str_xml) { ++ xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, ++ "xml namespace URI cannot be the default namespace\n", ++ NULL, NULL, NULL); ++ } ++ goto next_attr; ++ } ++ if ((len == 29) && ++ (xmlStrEqual(URL, ++ BAD_CAST "http://www.w3.org/2000/xmlns/"))) { ++ xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, ++ "reuse of the xmlns namespace name is forbidden\n", ++ NULL, NULL, NULL); ++ goto next_attr; ++ } ++ } ++ /* ++ * check that it's not a defined namespace ++ */ ++ for (j = 1;j <= nbNs;j++) ++ if (ctxt->nsTab[ctxt->nsNr - 2 * j] == NULL) ++ break; ++ if (j <= nbNs) ++ xmlErrAttributeDup(ctxt, NULL, attname); ++ else ++ if (nsPush(ctxt, NULL, URL) > 0) nbNs++; ++ ++ } else if (aprefix == ctxt->str_xmlns) { ++ const xmlChar *URL = xmlDictLookup(ctxt->dict, attvalue, len); ++ xmlURIPtr uri; ++ ++ if (attname == ctxt->str_xml) { ++ if (URL != ctxt->str_xml_ns) { ++ xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, ++ "xml namespace prefix mapped to wrong URI\n", ++ NULL, NULL, NULL); ++ } ++ /* ++ * Do not keep a namespace definition node ++ */ ++ goto next_attr; ++ } ++ if (URL == ctxt->str_xml_ns) { ++ if (attname != ctxt->str_xml) { ++ xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, ++ "xml namespace URI mapped to wrong prefix\n", ++ NULL, NULL, NULL); ++ } ++ goto next_attr; ++ } ++ if (attname == ctxt->str_xmlns) { ++ xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, ++ "redefinition of the xmlns prefix is forbidden\n", ++ NULL, NULL, NULL); ++ goto next_attr; ++ } ++ if ((len == 29) && ++ (xmlStrEqual(URL, ++ BAD_CAST "http://www.w3.org/2000/xmlns/"))) { ++ xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, ++ "reuse of the xmlns namespace name is forbidden\n", ++ NULL, NULL, NULL); ++ goto next_attr; ++ } ++ if ((URL == NULL) || (URL[0] == 0)) { ++ xmlNsErr(ctxt, XML_NS_ERR_XML_NAMESPACE, ++ "xmlns:%s: Empty XML namespace is not allowed\n", ++ attname, NULL, NULL); ++ goto next_attr; ++ } else { ++ uri = xmlParseURI((const char *) URL); ++ if (uri == NULL) { ++ xmlNsErr(ctxt, XML_WAR_NS_URI, ++ "xmlns:%s: '%s' is not a valid URI\n", ++ attname, URL, NULL); ++ } else { ++ if ((ctxt->pedantic) && (uri->scheme == NULL)) { ++ xmlNsWarn(ctxt, XML_WAR_NS_URI_RELATIVE, ++ "xmlns:%s: URI %s is not absolute\n", ++ attname, URL, NULL); ++ } ++ xmlFreeURI(uri); ++ } ++ } + +- /* +- * Add the pair to atts +- */ +- if ((atts == NULL) || (nbatts + 5 > maxatts)) { +- if (xmlCtxtGrowAttrs(ctxt, nbatts + 5) < 0) { +- if (attvalue[len] == 0) +- xmlFree(attvalue); +- goto failed; +- } +- maxatts = ctxt->maxatts; +- atts = ctxt->atts; +- } +- ctxt->attallocs[nratts++] = alloc; +- atts[nbatts++] = attname; +- atts[nbatts++] = aprefix; +- atts[nbatts++] = NULL; /* the URI will be fetched later */ +- atts[nbatts++] = attvalue; +- attvalue += len; +- atts[nbatts++] = attvalue; +- /* +- * tag if some deallocation is needed +- */ +- if (alloc != 0) attval = 1; +- } else { +- if ((attvalue != NULL) && (attvalue[len] == 0)) +- xmlFree(attvalue); +- } ++ /* ++ * check that it's not a defined namespace ++ */ ++ for (j = 1;j <= nbNs;j++) ++ if (ctxt->nsTab[ctxt->nsNr - 2 * j] == attname) ++ break; ++ if (j <= nbNs) ++ xmlErrAttributeDup(ctxt, aprefix, attname); ++ else ++ if (nsPush(ctxt, attname, URL) > 0) nbNs++; ++ ++ } else { ++ /* ++ * Add the pair to atts ++ */ ++ if ((atts == NULL) || (nbatts + 5 > maxatts)) { ++ if (xmlCtxtGrowAttrs(ctxt, nbatts + 5) < 0) { ++ goto next_attr; ++ } ++ maxatts = ctxt->maxatts; ++ atts = ctxt->atts; ++ } ++ ctxt->attallocs[nratts++] = alloc; ++ atts[nbatts++] = attname; ++ atts[nbatts++] = aprefix; ++ /* ++ * The namespace URI field is used temporarily to point at the ++ * base of the current input buffer for non-alloced attributes. ++ * When the input buffer is reallocated, all the pointers become ++ * invalid, but they can be reconstructed later. ++ */ ++ if (alloc) ++ atts[nbatts++] = NULL; ++ else ++ atts[nbatts++] = ctxt->input->base; ++ atts[nbatts++] = attvalue; ++ attvalue += len; ++ atts[nbatts++] = attvalue; ++ /* ++ * tag if some deallocation is needed ++ */ ++ if (alloc != 0) attval = 1; ++ attvalue = NULL; /* moved into atts */ ++ } + +-failed: ++next_attr: ++ if ((attvalue != NULL) && (alloc != 0)) { ++ xmlFree(attvalue); ++ attvalue = NULL; ++ } + + GROW + if (ctxt->instate == XML_PARSER_EOF) + break; +- if ((ctxt->input->base != base) || (inputNr != ctxt->inputNr)) +- goto base_changed; + if ((RAW == '>') || (((RAW == '/') && (NXT(1) == '>')))) + break; + if (!IS_BLANK_CH(RAW)) { +@@ -9646,8 +9610,20 @@ failed: + break; + } + GROW; +- if ((ctxt->input->base != base) || (inputNr != ctxt->inputNr)) +- goto base_changed; ++ } ++ ++ /* Reconstruct attribute value pointers. */ ++ for (i = 0, j = 0; j < nratts; i += 5, j++) { ++ if (atts[i+2] != NULL) { ++ /* ++ * Arithmetic on dangling pointers is technically undefined ++ * behavior, but well... ++ */ ++ ptrdiff_t offset = ctxt->input->base - atts[i+2]; ++ atts[i+2] = NULL; /* Reset repurposed namespace URI */ ++ atts[i+3] += offset; /* value */ ++ atts[i+4] += offset; /* valuend */ ++ } + } + + /* +@@ -9804,34 +9780,6 @@ failed: + } + + return(localname); +- +-base_changed: +- /* +- * the attribute strings are valid iif the base didn't changed +- */ +- if (attval != 0) { +- for (i = 3,j = 0; j < nratts;i += 5,j++) +- if ((ctxt->attallocs[j] != 0) && (atts[i] != NULL)) +- xmlFree((xmlChar *) atts[i]); +- } +- +- /* +- * We can't switch from one entity to another in the middle +- * of a start tag +- */ +- if (inputNr != ctxt->inputNr) { +- xmlFatalErrMsg(ctxt, XML_ERR_ENTITY_BOUNDARY, +- "Start tag doesn't start and stop in the same entity\n"); +- return(NULL); +- } +- +- ctxt->input->cur = ctxt->input->base + cur; +- ctxt->input->line = oldline; +- ctxt->input->col = oldcol; +- if (ctxt->wellFormed == 1) { +- goto reparse; +- } +- return(NULL); + } + + /** +diff --git a/result/errors/759398.xml.err b/result/errors/759398.xml.err +index e08d9bf..f6036a3 100644 +--- a/result/errors/759398.xml.err ++++ b/result/errors/759398.xml.err +@@ -1,9 +1,12 @@ + ./test/errors/759398.xml:210: parser error : StartTag: invalid element name + need to worry about parsers whi ++ ^ ++./test/errors/759398.xml:316: parser error : Extra content at the end of the document ++ ++^ +diff --git a/result/errors/attr1.xml.err b/result/errors/attr1.xml.err +index 4f08538..c4c4fc8 100644 +--- a/result/errors/attr1.xml.err ++++ b/result/errors/attr1.xml.err +@@ -1,6 +1,9 @@ + ./test/errors/attr1.xml:2: parser error : AttValue: ' expected + + ^ +-./test/errors/attr1.xml:1: parser error : Extra content at the end of the document +-ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +- ^ ++./test/errors/attr2.xml:2: parser error : attributes construct error ++ ++^ ++./test/errors/attr2.xml:2: parser error : Couldn't find end of Start Tag foo line 1 ++ ++^ +diff --git a/result/errors/name2.xml.err b/result/errors/name2.xml.err +index a6649a1..8a6acee 100644 +--- a/result/errors/name2.xml.err ++++ b/result/errors/name2.xml.err +@@ -1,6 +1,9 @@ + ./test/errors/name2.xml:2: parser error : Specification mandate value for attribute foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + ^ +-./test/errors/name2.xml:1: parser error : Extra content at the end of the document +-