Bad news, when checking against recursive entities expansion problem
back when it was made official (c.f. the billion laught attack circa
2004) I had checked for the normal recursion, but when happening in
an attribute avlue the resource consumption is way faster and the
recursion detection in place is not sufficient to catch the problem.

  Basically when this happen within an attribute just checking for
a recursion depth is not sufficient, and the only good method I could
find was to count the number of entities replacement taking place while
parsing a given document, and drop parsing after half a million
substitution. I think it's a fair default processand what the patches
below implements for various libxml2 versions, but i can understand that
in some case that may be problematic. So i intend in the next release
(2.7.0 hopefully available soon) to add a parser flag removing the
hardcoded limits (there is also a maximum document depth in place).

  Distributions have been made aware of the problem for a couple of
weeks and updates should be available soon from normal update channels
I'm updating SVN with the fix too,

Daniel

-- 
Red Hat Virtualization group http://redhat.com/virtualization/
Daniel Veillard      | virtualization library  http://libvirt.org/
[EMAIL PROTECTED]  | libxml GNOME XML XSLT toolkit  http://xmlsoft.org/
http://veillard.com/ | Rpmfind RPM search engine  http://rpmfind.net/
Index: include/libxml/parser.h
===================================================================
--- include/libxml/parser.h     (revision 3771)
+++ include/libxml/parser.h     (working copy)
@@ -297,6 +297,7 @@ struct _xmlParserCtxt {
      */
     xmlError          lastError;
     xmlParserMode     parseMode;    /* the parser mode */
+    unsigned long    nbentities;    /* number of entities references */
 };
 
 /**
Index: include/libxml/entities.h
===================================================================
--- include/libxml/entities.h   (revision 3771)
+++ include/libxml/entities.h   (working copy)
@@ -57,6 +57,7 @@ struct _xmlEntity {
     const xmlChar           *URI;      /* the full URI as computed */
     int                    owner;      /* does the entity own the childrens */
     int                         checked;       /* was the entity content 
checked */
+    unsigned long     nbentities;      /* the number of entities references */
 };
 
 /*
Index: entities.c
===================================================================
--- entities.c  (revision 3771)
+++ entities.c  (working copy)
@@ -31,35 +31,35 @@ static xmlEntity xmlEntityLt = {
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "<", BAD_CAST "<", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0, 1
+    NULL, NULL, NULL, NULL, 0, 1, 0
 };
 static xmlEntity xmlEntityGt = {
     NULL, XML_ENTITY_DECL, BAD_CAST "gt",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST ">", BAD_CAST ">", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0, 1
+    NULL, NULL, NULL, NULL, 0, 1, 0
 };
 static xmlEntity xmlEntityAmp = {
     NULL, XML_ENTITY_DECL, BAD_CAST "amp",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "&", BAD_CAST "&", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0, 1
+    NULL, NULL, NULL, NULL, 0, 1, 0
 };
 static xmlEntity xmlEntityQuot = {
     NULL, XML_ENTITY_DECL, BAD_CAST "quot",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "\"", BAD_CAST "\"", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0, 1
+    NULL, NULL, NULL, NULL, 0, 1, 0
 };
 static xmlEntity xmlEntityApos = {
     NULL, XML_ENTITY_DECL, BAD_CAST "apos",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "'", BAD_CAST "'", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0, 1
+    NULL, NULL, NULL, NULL, 0, 1, 0
 };
 
 /**
Index: parserInternals.c
===================================================================
--- parserInternals.c   (revision 3771)
+++ parserInternals.c   (working copy)
@@ -1670,6 +1670,7 @@ xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
     ctxt->depth = 0;
     ctxt->charset = XML_CHAR_ENCODING_UTF8;
     ctxt->catalogs = NULL;
+    ctxt->nbentities = 0;
     xmlInitNodeInfoSeq(&ctxt->node_seq);
     return(0);
 }
Index: parser.c
===================================================================
--- parser.c    (revision 3771)
+++ parser.c    (working copy)
@@ -2379,7 +2379,7 @@ xmlStringLenDecodeEntities(xmlParserCtxt
        return(NULL);
     last = str + len;
 
-    if (ctxt->depth > 40) {
+    if ((ctxt->depth > 40) || (ctxt->nbentities >= 500000)) {
        xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
        return(NULL);
     }
@@ -2417,6 +2417,11 @@ xmlStringLenDecodeEntities(xmlParserCtxt
                        "String decoding Entity Reference: %.30s\n",
                        str);
            ent = xmlParseStringEntityRef(ctxt, &str);
+           if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if ((ent != NULL) &&
                (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
                if (ent->content != NULL) {
@@ -2462,6 +2467,11 @@ xmlStringLenDecodeEntities(xmlParserCtxt
                xmlGenericError(xmlGenericErrorContext,
                        "String decoding PE Reference: %.30s\n", str);
            ent = xmlParseStringPEReference(ctxt, &str);
+           if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if (ent != NULL) {
                 if (ent->content == NULL) {
                    if (xmlLoadEntityContent(ctxt, ent) < 0) {
@@ -2501,6 +2511,7 @@ xmlStringLenDecodeEntities(xmlParserCtxt
 
 mem_error:
     xmlErrMemory(ctxt, NULL);
+int_error:
     if (rep != NULL)
         xmlFree(rep);
     if (buffer != NULL)
@@ -3542,6 +3553,9 @@ xmlParseAttValueComplex(xmlParserCtxtPtr
                }
            } else {
                ent = xmlParseEntityRef(ctxt);
+               ctxt->nbentities++;
+               if (ent != NULL)
+                   ctxt->nbentities += ent->nbentities;
                if ((ent != NULL) &&
                    (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
                    if (len > buf_size - 10) {
@@ -4844,6 +4858,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
     int isParameter = 0;
     xmlChar *orig = NULL;
     int skipped;
+    unsigned long oldnbent = ctxt->nbentities;
     
     /* GROW; done in the caller */
     if (CMP8(CUR_PTR, '<', '!', 'E', 'N', 'T', 'I', 'T', 'Y')) {
@@ -5068,6 +5083,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
                }
            }
             if (cur != NULL) {
+               cur->nbentities = ctxt->nbentities - oldnbent;
                if (cur->orig != NULL)
                    xmlFree(orig);
                else
@@ -6477,6 +6493,11 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
        if (ent == NULL) return;
        if (!ctxt->wellFormed)
            return;
+       ctxt->nbentities++;
+       if (ctxt->nbentities >= 500000) {
+           xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
+           return;
+       }
        was_checked = ent->checked;
        if ((ent->name != NULL) && 
            (ent->etype != XML_INTERNAL_PREDEFINED_ENTITY)) {
@@ -6537,6 +6558,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        xmlFreeNodeList(list);
                    }
                } else {
+                   unsigned long oldnbent = ctxt->nbentities;
                    /*
                     * 4.3.2: An internal general parsed entity is well-formed
                     * if its replacement text matches the production labeled
@@ -6559,6 +6581,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        ret = xmlParseBalancedChunkMemoryInternal(ctxt,
                                           value, user_data, &list);
                        ctxt->depth--;
+
                    } else if (ent->etype ==
                               XML_EXTERNAL_GENERAL_PARSED_ENTITY) {
                        ctxt->depth++;
@@ -6571,6 +6594,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        xmlErrMsgStr(ctxt, XML_ERR_INTERNAL_ERROR,
                                     "invalid entity type found\n", NULL);
                    }
+                   ent->nbentities = ctxt->nbentities - oldnbent;
                    if (ret == XML_ERR_ENTITY_LOOP) {
                        xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
                        return;
@@ -6629,6 +6653,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                }
                ent->checked = 1;
            }
+           ctxt->nbentities += ent->nbentities;
 
             if (ent->children == NULL) {
                /*
@@ -11800,7 +11825,7 @@ xmlParseCtxtExternalEntity(xmlParserCtxt
 
     if (ctx == NULL) return(-1);
 
-    if (ctx->depth > 40) {
+    if ((ctx->depth > 40) || (ctx->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -12010,7 +12035,8 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     xmlChar start[4];
     xmlCharEncoding enc;
 
-    if (depth > 40) {
+    if ((depth > 40) ||
+        ((oldctxt != NULL) && (oldctxt->nbentities >= 500000))) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -12154,6 +12180,7 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     oldctxt->node_seq.maximum = ctxt->node_seq.maximum;
     oldctxt->node_seq.length = ctxt->node_seq.length;
     oldctxt->node_seq.buffer = ctxt->node_seq.buffer;
+    oldctxt->nbentities += ctxt->nbentities;
     ctxt->node_seq.maximum = 0;
     ctxt->node_seq.length = 0;
     ctxt->node_seq.buffer = NULL;
@@ -12254,7 +12281,7 @@ xmlParseBalancedChunkMemoryInternal(xmlP
     int size;
     xmlParserErrors ret = XML_ERR_OK;
 
-    if (oldctxt->depth > 40) {
+    if ((oldctxt->depth > 40) || (oldctxt->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -12379,6 +12406,7 @@ xmlParseBalancedChunkMemoryInternal(xmlP
         ctxt->myDoc->last = last;
     }
        
+    oldctxt->nbentities += ctxt->nbentities;
     ctxt->sax = oldsax;
     ctxt->dict = NULL;
     ctxt->attsDefault = NULL;
@@ -13695,6 +13723,7 @@ xmlCtxtReset(xmlParserCtxtPtr ctxt)
     ctxt->depth = 0;
     ctxt->charset = XML_CHAR_ENCODING_UTF8;
     ctxt->catalogs = NULL;
+    ctxt->nbentities = 0;
     xmlInitNodeInfoSeq(&ctxt->node_seq);
 
     if (ctxt->attsDefault != NULL) {
--- include/libxml/parser.h     (revision 3771)
+++ include/libxml/parser.h     (working copy)
@@ -297,6 +297,7 @@ struct _xmlParserCtxt {
      */
     xmlError          lastError;
     xmlParserMode     parseMode;    /* the parser mode */
+    unsigned long    nbentities;    /* number of entities references */
 };
 
 /**
--- include/libxml/entities.h.orig      2005-01-04 15:49:49.000000000 +0100
+++ include/libxml/entities.h   2008-08-11 17:56:53.000000000 +0200
@@ -56,6 +56,7 @@ struct _xmlEntity {
     struct _xmlEntity     *nexte;      /* unused */
     const xmlChar           *URI;      /* the full URI as computed */
     int                    owner;      /* does the entity own the childrens */
+    unsigned long     nbentities;      /* the number of entities references */
 };
 
 /*
--- entities.c.orig     2006-03-09 17:39:46.000000000 +0100
+++ entities.c  2008-08-11 18:01:06.000000000 +0200
@@ -31,35 +31,35 @@ static xmlEntity xmlEntityLt = {
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "<", BAD_CAST "<", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 static xmlEntity xmlEntityGt = {
     NULL, XML_ENTITY_DECL, BAD_CAST "gt",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST ">", BAD_CAST ">", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 static xmlEntity xmlEntityAmp = {
     NULL, XML_ENTITY_DECL, BAD_CAST "amp",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "&", BAD_CAST "&", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 static xmlEntity xmlEntityQuot = {
     NULL, XML_ENTITY_DECL, BAD_CAST "quot",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "\"", BAD_CAST "\"", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 static xmlEntity xmlEntityApos = {
     NULL, XML_ENTITY_DECL, BAD_CAST "apos",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "'", BAD_CAST "'", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 
 /**
--- parser.c.orig       2006-04-23 11:39:15.000000000 +0200
+++ parser.c    2008-08-11 18:36:56.000000000 +0200
@@ -2174,7 +2176,7 @@ xmlStringLenDecodeEntities(xmlParserCtxt
        return(NULL);
     last = str + len;
 
-    if (ctxt->depth > 40) {
+    if ((ctxt->depth > 40) || (ctxt->nbentities >= 500000)) {
        xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
        return(NULL);
     }
@@ -2212,6 +2214,11 @@ xmlStringLenDecodeEntities(xmlParserCtxt
                        "String decoding Entity Reference: %.30s\n",
                        str);
            ent = xmlParseStringEntityRef(ctxt, &str);
+           if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if ((ent != NULL) &&
                (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
                if (ent->content != NULL) {
@@ -2258,6 +2265,11 @@ xmlStringLenDecodeEntities(xmlParserCtxt
                xmlGenericError(xmlGenericErrorContext,
                        "String decoding PE Reference: %.30s\n", str);
            ent = xmlParseStringPEReference(ctxt, &str);
+           if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if (ent != NULL) {
                xmlChar *rep;
 
@@ -2294,6 +2306,9 @@ xmlStringLenDecodeEntities(xmlParserCtxt
 
 mem_error:
     xmlErrMemory(ctxt, NULL);
+int_error:
+    if (buffer != NULL)
+        xmlFree(buffer);
     return(NULL);
 }
 
@@ -3100,6 +3115,9 @@ xmlParseAttValueComplex(xmlParserCtxtPtr
                }
            } else {
                ent = xmlParseEntityRef(ctxt);
+               ctxt->nbentities++;
+               if (ent != NULL)
+                   ctxt->nbentities += ent->nbentities;
                if ((ent != NULL) &&
                    (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
                    if (len > buf_size - 10) {
@@ -4342,6 +4360,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
     int isParameter = 0;
     xmlChar *orig = NULL;
     int skipped;
+    unsigned long oldnbent = ctxt->nbentities;
     
     /* GROW; done in the caller */
     if (CMP8(CUR_PTR, '<', '!', 'E', 'N', 'T', 'I', 'T', 'Y')) {
@@ -4551,6 +4570,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
                }
            }
             if (cur != NULL) {
+               cur->nbentities = ctxt->nbentities - oldnbent;
                if (cur->orig != NULL)
                    xmlFree(orig);
                else
@@ -5927,6 +5947,11 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
        if (ent == NULL) return;
        if (!ctxt->wellFormed)
            return;
+       ctxt->nbentities++;
+       if (ctxt->nbentities >= 500000) {
+           xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
+           return;
+       }
        if ((ent->name != NULL) && 
            (ent->etype != XML_INTERNAL_PREDEFINED_ENTITY)) {
            xmlNodePtr list = NULL;
@@ -5985,6 +6010,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        xmlFreeNodeList(list);
                    }
                } else {
+                   unsigned long oldnbent = ctxt->nbentities;
                    /*
                     * 4.3.2: An internal general parsed entity is well-formed
                     * if its replacement text matches the production labeled
@@ -6007,6 +6033,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        ret = xmlParseBalancedChunkMemoryInternal(ctxt,
                                           value, user_data, &list);
                        ctxt->depth--;
+
                    } else if (ent->etype ==
                               XML_EXTERNAL_GENERAL_PARSED_ENTITY) {
                        ctxt->depth++;
@@ -6019,6 +6046,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        xmlErrMsgStr(ctxt, XML_ERR_INTERNAL_ERROR,
                                     "invalid entity type found\n", NULL);
                    }
+                   ent->nbentities = ctxt->nbentities - oldnbent;
                    if (ret == XML_ERR_ENTITY_LOOP) {
                        xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
                        return;
@@ -6075,6 +6103,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                    }
                }
            }
+           ctxt->nbentities += ent->nbentities;
            if ((ctxt->sax != NULL) && (ctxt->sax->reference != NULL) &&
                (ctxt->replaceEntities == 0) && (!ctxt->disableSAX)) {
                /*
@@ -11035,7 +11064,7 @@ xmlParseCtxtExternalEntity(xmlParserCtxt
 
     if (ctx == NULL) return(-1);
 
-    if (ctx->depth > 40) {
+    if ((ctx->depth > 40) || (ctx->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -11220,7 +11249,8 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     xmlChar start[4];
     xmlCharEncoding enc;
 
-    if (depth > 40) {
+    if ((depth > 40) ||
+        ((oldctxt != NULL) && (oldctxt->nbentities >= 500000))) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -11363,6 +11393,7 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     oldctxt->node_seq.maximum = ctxt->node_seq.maximum;
     oldctxt->node_seq.length = ctxt->node_seq.length;
     oldctxt->node_seq.buffer = ctxt->node_seq.buffer;
+    oldctxt->nbentities += ctxt->nbentities;
     ctxt->node_seq.maximum = 0;
     ctxt->node_seq.length = 0;
     ctxt->node_seq.buffer = NULL;
@@ -11463,7 +11494,7 @@ xmlParseBalancedChunkMemoryInternal(xmlP
     int size;
     xmlParserErrors ret = XML_ERR_OK;
 
-    if (oldctxt->depth > 40) {
+    if ((oldctxt->depth > 40) || (oldctxt->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -11587,6 +11618,7 @@ xmlParseBalancedChunkMemoryInternal(xmlP
         ctxt->myDoc->last = last;
     }
        
+    oldctxt->nbentities += ctxt->nbentities;
     ctxt->sax = oldsax;
     ctxt->dict = NULL;
     ctxt->attsDefault = NULL;
@@ -12883,6 +12915,7 @@ xmlCtxtReset(xmlParserCtxtPtr ctxt)
     ctxt->depth = 0;
     ctxt->charset = XML_CHAR_ENCODING_UTF8;
     ctxt->catalogs = NULL;
+    ctxt->nbentities = 0;
     xmlInitNodeInfoSeq(&ctxt->node_seq);
 
     if (ctxt->attsDefault != NULL) {
--- include/libxml/parser.h     (revision 3771)
+++ include/libxml/parser.h     (working copy)
@@ -297,6 +297,7 @@ struct _xmlParserCtxt {
      */
     xmlError          lastError;
     xmlParserMode     parseMode;    /* the parser mode */
+    unsigned long    nbentities;    /* number of entities references */
 };
 
 /**
--- include/libxml/entities.h.orig      2005-01-04 15:49:49.000000000 +0100
+++ include/libxml/entities.h   2008-08-11 17:56:53.000000000 +0200
@@ -56,6 +56,7 @@ struct _xmlEntity {
     struct _xmlEntity     *nexte;      /* unused */
     const xmlChar           *URI;      /* the full URI as computed */
     int                    owner;      /* does the entity own the childrens */
+    unsigned long     nbentities;      /* the number of entities references */
 };
 
 /*
--- entities.c.orig     2006-03-09 17:39:46.000000000 +0100
+++ entities.c  2008-08-11 18:01:06.000000000 +0200
@@ -31,35 +31,35 @@ static xmlEntity xmlEntityLt = {
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "<", BAD_CAST "<", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 static xmlEntity xmlEntityGt = {
     NULL, XML_ENTITY_DECL, BAD_CAST "gt",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST ">", BAD_CAST ">", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 static xmlEntity xmlEntityAmp = {
     NULL, XML_ENTITY_DECL, BAD_CAST "amp",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "&", BAD_CAST "&", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 static xmlEntity xmlEntityQuot = {
     NULL, XML_ENTITY_DECL, BAD_CAST "quot",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "\"", BAD_CAST "\"", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 static xmlEntity xmlEntityApos = {
     NULL, XML_ENTITY_DECL, BAD_CAST "apos",
     NULL, NULL, NULL, NULL, NULL, NULL, 
     BAD_CAST "'", BAD_CAST "'", 1,
     XML_INTERNAL_PREDEFINED_ENTITY,
-    NULL, NULL, NULL, NULL, 0
+    NULL, NULL, NULL, NULL, 0, 0
 };
 
 /**
--- parser.c.orig       2006-04-23 11:39:15.000000000 +0200
+++ parser.c    2008-08-11 18:36:56.000000000 +0200
@@ -2174,7 +2176,7 @@ xmlStringLenDecodeEntities(xmlParserCtxt
        return(NULL);
     last = str + len;
 
-    if (ctxt->depth > 40) {
+    if ((ctxt->depth > 40) || (ctxt->nbentities >= 500000)) {
        xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
        return(NULL);
     }
@@ -2212,6 +2214,11 @@ xmlStringLenDecodeEntities(xmlParserCtxt
                        "String decoding Entity Reference: %.30s\n",
                        str);
            ent = xmlParseStringEntityRef(ctxt, &str);
+           if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if ((ent != NULL) &&
                (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
                if (ent->content != NULL) {
@@ -2258,6 +2265,11 @@ xmlStringLenDecodeEntities(xmlParserCtxt
                xmlGenericError(xmlGenericErrorContext,
                        "String decoding PE Reference: %.30s\n", str);
            ent = xmlParseStringPEReference(ctxt, &str);
+           if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if (ent != NULL) {
                xmlChar *rep;
 
@@ -2294,6 +2306,9 @@ xmlStringLenDecodeEntities(xmlParserCtxt
 
 mem_error:
     xmlErrMemory(ctxt, NULL);
+int_error:
+    if (buffer != NULL)
+        xmlFree(buffer);
     return(NULL);
 }
 
@@ -3100,6 +3115,9 @@ xmlParseAttValueComplex(xmlParserCtxtPtr
                }
            } else {
                ent = xmlParseEntityRef(ctxt);
+               ctxt->nbentities++;
+               if (ent != NULL)
+                   ctxt->nbentities += ent->nbentities;
                if ((ent != NULL) &&
                    (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
                    if (len > buf_size - 10) {
@@ -4342,6 +4360,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
     int isParameter = 0;
     xmlChar *orig = NULL;
     int skipped;
+    unsigned long oldnbent = ctxt->nbentities;
     
     /* GROW; done in the caller */
     if (CMP8(CUR_PTR, '<', '!', 'E', 'N', 'T', 'I', 'T', 'Y')) {
@@ -4551,6 +4570,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
                }
            }
             if (cur != NULL) {
+               cur->nbentities = ctxt->nbentities - oldnbent;
                if (cur->orig != NULL)
                    xmlFree(orig);
                else
@@ -5927,6 +5947,11 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
        if (ent == NULL) return;
        if (!ctxt->wellFormed)
            return;
+       ctxt->nbentities++;
+       if (ctxt->nbentities >= 500000) {
+           xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
+           return;
+       }
        if ((ent->name != NULL) && 
            (ent->etype != XML_INTERNAL_PREDEFINED_ENTITY)) {
            xmlNodePtr list = NULL;
@@ -5985,6 +6010,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        xmlFreeNodeList(list);
                    }
                } else {
+                   unsigned long oldnbent = ctxt->nbentities;
                    /*
                     * 4.3.2: An internal general parsed entity is well-formed
                     * if its replacement text matches the production labeled
@@ -6007,6 +6033,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        ret = xmlParseBalancedChunkMemoryInternal(ctxt,
                                           value, user_data, &list);
                        ctxt->depth--;
+
                    } else if (ent->etype ==
                               XML_EXTERNAL_GENERAL_PARSED_ENTITY) {
                        ctxt->depth++;
@@ -6019,6 +6046,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        xmlErrMsgStr(ctxt, XML_ERR_INTERNAL_ERROR,
                                     "invalid entity type found\n", NULL);
                    }
+                   ent->nbentities = ctxt->nbentities - oldnbent;
                    if (ret == XML_ERR_ENTITY_LOOP) {
                        xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
                        return;
@@ -6075,6 +6103,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                    }
                }
            }
+           ctxt->nbentities += ent->nbentities;
            if ((ctxt->sax != NULL) && (ctxt->sax->reference != NULL) &&
                (ctxt->replaceEntities == 0) && (!ctxt->disableSAX)) {
                /*
@@ -11035,7 +11064,7 @@ xmlParseCtxtExternalEntity(xmlParserCtxt
 
     if (ctx == NULL) return(-1);
 
-    if (ctx->depth > 40) {
+    if ((ctx->depth > 40) || (ctx->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -11220,7 +11249,8 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     xmlChar start[4];
     xmlCharEncoding enc;
 
-    if (depth > 40) {
+    if ((depth > 40) ||
+        ((oldctxt != NULL) && (oldctxt->nbentities >= 500000))) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -11363,6 +11393,7 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     oldctxt->node_seq.maximum = ctxt->node_seq.maximum;
     oldctxt->node_seq.length = ctxt->node_seq.length;
     oldctxt->node_seq.buffer = ctxt->node_seq.buffer;
+    oldctxt->nbentities += ctxt->nbentities;
     ctxt->node_seq.maximum = 0;
     ctxt->node_seq.length = 0;
     ctxt->node_seq.buffer = NULL;
@@ -11463,7 +11494,7 @@ xmlParseBalancedChunkMemoryInternal(xmlP
     int size;
     xmlParserErrors ret = XML_ERR_OK;
 
-    if (oldctxt->depth > 40) {
+    if ((oldctxt->depth > 40) || (oldctxt->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -11587,6 +11618,7 @@ xmlParseBalancedChunkMemoryInternal(xmlP
         ctxt->myDoc->last = last;
     }
        
+    oldctxt->nbentities += ctxt->nbentities;
     ctxt->sax = oldsax;
     ctxt->dict = NULL;
     ctxt->attsDefault = NULL;
@@ -12883,6 +12915,7 @@ xmlCtxtReset(xmlParserCtxtPtr ctxt)
     ctxt->depth = 0;
     ctxt->charset = XML_CHAR_ENCODING_UTF8;
     ctxt->catalogs = NULL;
+    ctxt->nbentities = 0;
     xmlInitNodeInfoSeq(&ctxt->node_seq);
 
     if (ctxt->attsDefault != NULL) {
--- include/libxml/entities.h.orig      2005-01-04 15:49:49.000000000 +0100
+++ include/libxml/entities.h   2008-08-11 17:56:53.000000000 +0200
@@ -56,6 +56,7 @@ struct _xmlEntity {
     struct _xmlEntity     *nexte;      /* unused */
     const xmlChar           *URI;      /* the full URI as computed */
     int                    owner;      /* does the entity own the childrens */
+    unsigned long     nbentities;      /* the number of entities references */
 };
 
 /*
--- include/libxml/parser.h.orig        2003-04-24 14:04:00.000000000 +0200
+++ include/libxml/parser.h     2008-08-11 19:03:50.000000000 +0200
@@ -234,6 +234,7 @@ struct _xmlParserCtxt {
     void              *catalogs;       /* document's own catalog */
     int                recovery;      /* run in recovery mode */
     int                progressive;   /* is this a progressive parsing */
+    unsigned long      nbentities;    /* number of entities references */
 };
 
 /**
--- parser.c.orig       2003-08-15 01:42:29.000000000 +0200
+++ parser.c    2008-08-11 19:28:59.000000000 +0200
@@ -1061,7 +1061,7 @@ xmlStringDecodeEntities(xmlParserCtxtPtr
     if (str == NULL)
        return(NULL);
 
-    if (ctxt->depth > 40) {
+    if ((ctxt->depth > 40) || (ctxt->nbentities >= 500000)) {
        ctxt->errNo = XML_ERR_ENTITY_LOOP;
        if ((ctxt->sax != NULL) && (ctxt->sax->error != NULL))
            ctxt->sax->error(ctxt->userData,
@@ -1102,6 +1102,11 @@ xmlStringDecodeEntities(xmlParserCtxtPtr
                        "String decoding Entity Reference: %.30s\n",
                        str);
            ent = xmlParseStringEntityRef(ctxt, &str);
+           if (ctxt->errNo == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if ((ent != NULL) &&
                (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
                if (ent->content != NULL) {
@@ -1146,6 +1151,11 @@ xmlStringDecodeEntities(xmlParserCtxtPtr
                xmlGenericError(xmlGenericErrorContext,
                        "String decoding PE Reference: %.30s\n", str);
            ent = xmlParseStringPEReference(ctxt, &str);
+           if (ctxt->errNo == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if (ent != NULL) {
                xmlChar *rep;
 
@@ -1176,6 +1186,10 @@ xmlStringDecodeEntities(xmlParserCtxtPtr
     }
     buffer[nbchars++] = 0;
     return(buffer);
+int_error:
+    if (buffer != NULL)
+        xmlFree(buffer);
+    return(NULL);
 }
 
 
@@ -2527,6 +2541,9 @@ xmlParseAttValueComplex(xmlParserCtxtPtr
                }
            } else {
                ent = xmlParseEntityRef(ctxt);
+               ctxt->nbentities++;
+               if (ent != NULL)
+                   ctxt->nbentities += ent->nbentities;
                if ((ent != NULL) && 
                    (ctxt->replaceEntities != 0)) {
                    xmlChar *rep;
@@ -3595,6 +3612,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
     int isParameter = 0;
     xmlChar *orig = NULL;
     int skipped;
+    unsigned long oldnbent = ctxt->nbentities;
     
     GROW;
     if ((RAW == '<') && (NXT(1) == '!') &&
@@ -3870,6 +3888,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
                }
            }
             if (cur != NULL) {
+               cur->nbentities = ctxt->nbentities - oldnbent;
                if (cur->orig != NULL)
                    xmlFree(orig);
                else
@@ -5480,6 +5499,16 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
        if (ent == NULL) return;
        if (!ctxt->wellFormed)
            return;
+       ctxt->nbentities++;
+       if (ctxt->nbentities >= 500000) {
+           ctxt->errNo = XML_ERR_ENTITY_LOOP;
+           if ((ctxt->sax != NULL) && (ctxt->sax->error != NULL))
+               ctxt->sax->error(ctxt->userData,
+                   "Detected entity reference loop\n");
+           ctxt->wellFormed = 0;
+           if (ctxt->recovery == 0) ctxt->disableSAX = 1;
+           return;
+       }
        if ((ent->name != NULL) && 
            (ent->etype != XML_INTERNAL_PREDEFINED_ENTITY)) {
            xmlNodePtr list = NULL;
@@ -5538,6 +5567,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        xmlFreeNodeList(list);
                    }
                } else {
+                   unsigned long oldnbent = ctxt->nbentities;
                    /*
                     * 4.3.2: An internal general parsed entity is well-formed
                     * if its replacement text matches the production labeled
@@ -5560,6 +5590,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        ret = xmlParseBalancedChunkMemoryInternal(ctxt,
                                           value, user_data, &list);
                        ctxt->depth--;
+
                    } else if (ent->etype ==
                               XML_EXTERNAL_GENERAL_PARSED_ENTITY) {
                        ctxt->depth++;
@@ -5573,6 +5604,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                            ctxt->sax->error(ctxt->userData,
                                "Internal: invalid entity type\n");
                    }
+                   ent->nbentities = ctxt->nbentities - oldnbent;
                    if (ret == XML_ERR_ENTITY_LOOP) {
                        ctxt->errNo = XML_ERR_ENTITY_LOOP;
                        if ((ctxt->sax != NULL) && (ctxt->sax->error != NULL))
@@ -5635,6 +5667,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                    }
                }
            }
+           ctxt->nbentities += ent->nbentities;
            if ((ctxt->sax != NULL) && (ctxt->sax->reference != NULL) &&
                (ctxt->replaceEntities == 0) && (!ctxt->disableSAX)) {
                /*
@@ -9713,7 +9746,7 @@ xmlParseCtxtExternalEntity(xmlParserCtxt
     xmlChar start[4];
     xmlCharEncoding enc;
 
-    if (ctx->depth > 40) {
+    if ((ctx->depth > 40) || (ctx->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -9894,7 +9927,8 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     xmlChar start[4];
     xmlCharEncoding enc;
 
-    if (depth > 40) {
+    if ((depth > 40) ||
+        ((oldctxt != NULL) && (oldctxt->nbentities >= 500000))) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -10053,6 +10087,7 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     oldctxt->node_seq.maximum = ctxt->node_seq.maximum;
     oldctxt->node_seq.length = ctxt->node_seq.length;
     oldctxt->node_seq.buffer = ctxt->node_seq.buffer;
+    oldctxt->nbentities += ctxt->nbentities;
     ctxt->node_seq.maximum = 0;
     ctxt->node_seq.length = 0;
     ctxt->node_seq.buffer = NULL;
@@ -10149,7 +10184,7 @@ xmlParseBalancedChunkMemoryInternal(xmlP
     int size;
     int ret = 0;
 
-    if (oldctxt->depth > 40) {
+    if ((oldctxt->depth > 40) || (oldctxt->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -10264,6 +10299,7 @@ xmlParseBalancedChunkMemoryInternal(xmlP
         ctxt->myDoc->children = content;
     }
        
+    oldctxt->nbentities += ctxt->nbentities;
     ctxt->sax = oldsax;
     xmlFreeParserCtxt(ctxt);
     if (newDoc != NULL)
--- include/libxml/entities.h.orig      2002-03-13 04:35:14.000000000 +0100
+++ include/libxml/entities.h   2008-08-11 19:40:53.000000000 +0200
@@ -52,6 +52,7 @@ struct _xmlEntity {
 
     struct _xmlEntity     *nexte;      /* unused */
     const xmlChar           *URI;      /* the full URI as computed */
+    unsigned long     nbentities;      /* the number of entities references */
 };
 
 /*
--- include/libxml/parser.h.orig        2002-03-13 04:35:14.000000000 +0100
+++ include/libxml/parser.h     2008-08-11 19:41:53.000000000 +0200
@@ -219,6 +219,7 @@ struct _xmlParserCtxt {
     int                loadsubset;    /* should the external subset be loaded 
*/
     int                linenumbers;   /* set line number in element content */
     void              *catalogs;       /* document's own catalog */
+    unsigned long      nbentities;    /* number of entities references */
 };
 
 /**
--- parser.c.orig       2002-03-21 04:35:11.000000000 +0100
+++ parser.c    2008-08-11 19:53:04.000000000 +0200
@@ -919,7 +919,7 @@ xmlStringDecodeEntities(xmlParserCtxtPtr
     if (str == NULL)
        return(NULL);
 
-    if (ctxt->depth > 40) {
+    if ((ctxt->depth > 40) || (ctxt->nbentities >= 500000)) {
        ctxt->errNo = XML_ERR_ENTITY_LOOP;
        if ((ctxt->sax != NULL) && (ctxt->sax->error != NULL))
            ctxt->sax->error(ctxt->userData,
@@ -959,6 +959,11 @@ xmlStringDecodeEntities(xmlParserCtxtPtr
                        "String decoding Entity Reference: %.30s\n",
                        str);
            ent = xmlParseStringEntityRef(ctxt, &str);
+           if (ctxt->errNo == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if ((ent != NULL) &&
                (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)) {
                if (ent->content != NULL) {
@@ -1003,6 +1008,11 @@ xmlStringDecodeEntities(xmlParserCtxtPtr
                xmlGenericError(xmlGenericErrorContext,
                        "String decoding PE Reference: %.30s\n", str);
            ent = xmlParseStringPEReference(ctxt, &str);
+           if (ctxt->errNo == XML_ERR_ENTITY_LOOP)
+               goto int_error;
+           ctxt->nbentities++;
+           if (ent != NULL)
+               ctxt->nbentities += ent->nbentities;
            if (ent != NULL) {
                xmlChar *rep;
 
@@ -1033,6 +1043,10 @@ xmlStringDecodeEntities(xmlParserCtxtPtr
     }
     buffer[nbchars++] = 0;
     return(buffer);
+int_error:
+    if (buffer != NULL)
+        xmlFree(buffer);
+    return(NULL);
 }
 
 
@@ -2279,6 +2293,9 @@ xmlParseAttValue(xmlParserCtxtPtr ctxt) 
                }
            } else {
                ent = xmlParseEntityRef(ctxt);
+               ctxt->nbentities++;
+               if (ent != NULL)
+                   ctxt->nbentities += ent->nbentities;
                if ((ent != NULL) && 
                    (ctxt->replaceEntities != 0)) {
                    xmlChar *rep;
@@ -3337,6 +3354,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
     xmlChar *ndata = NULL;
     int isParameter = 0;
     xmlChar *orig = NULL;
+    unsigned long oldnbent = ctxt->nbentities;
     
     GROW;
     if ((RAW == '<') && (NXT(1) == '!') &&
@@ -3612,6 +3630,7 @@ xmlParseEntityDecl(xmlParserCtxtPtr ctxt
                }
            }
             if (cur != NULL) {
+               cur->nbentities = ctxt->nbentities - oldnbent;
                if (cur->orig != NULL)
                    xmlFree(orig);
                else
@@ -5228,6 +5247,15 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
        if (ent == NULL) return;
        if (!ctxt->wellFormed)
            return;
+       ctxt->nbentities++;
+       if (ctxt->nbentities >= 500000) {
+           ctxt->errNo = XML_ERR_ENTITY_LOOP;
+           if ((ctxt->sax != NULL) && (ctxt->sax->error != NULL))
+               ctxt->sax->error(ctxt->userData,
+                   "Detected entity reference loop\n");
+           ctxt->wellFormed = 0;
+           return;
+       }
        if ((ent->name != NULL) && 
            (ent->etype != XML_INTERNAL_PREDEFINED_ENTITY)) {
            xmlNodePtr list = NULL;
@@ -5285,6 +5313,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                        xmlFreeNodeList(list);
                    }
                } else {
+                   unsigned long oldnbent = ctxt->nbentities;
                    /*
                     * 4.3.2: An internal general parsed entity is well-formed
                     * if its replacement text matches the production labeled
@@ -5308,6 +5337,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                                           ctxt->sax, user_data, ctxt->depth,
                                           value, &list);
                        ctxt->depth--;
+
                    } else if (ent->etype ==
                               XML_EXTERNAL_GENERAL_PARSED_ENTITY) {
                        ctxt->depth++;
@@ -5321,6 +5351,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                            ctxt->sax->error(ctxt->userData,
                                "Internal: invalid entity type\n");
                    }
+                   ent->nbentities = ctxt->nbentities - oldnbent;
                    if (ret == XML_ERR_ENTITY_LOOP) {
                        ctxt->errNo = XML_ERR_ENTITY_LOOP;
                        if ((ctxt->sax != NULL) && (ctxt->sax->error != NULL))
@@ -5379,6 +5410,7 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                    }
                }
            }
+           ctxt->nbentities += ent->nbentities;
            if ((ctxt->sax != NULL) && (ctxt->sax->reference != NULL) &&
                (ctxt->replaceEntities == 0) && (!ctxt->disableSAX)) {
                /*
@@ -9251,7 +9283,7 @@ xmlParseCtxtExternalEntity(xmlParserCtxt
     xmlChar start[4];
     xmlCharEncoding enc;
 
-    if (ctx->depth > 40) {
+    if ((ctx->depth > 40) || (ctx->nbentities >= 500000)) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -9428,7 +9460,8 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
     xmlChar start[4];
     xmlCharEncoding enc;
 
-    if (depth > 40) {
+    if ((depth > 40) ||
+        ((oldctxt != NULL) && (oldctxt->nbentities >= 500000))) {
        return(XML_ERR_ENTITY_LOOP);
     }
 
@@ -9572,6 +9605,7 @@ xmlParseExternalEntityPrivate(xmlDocPtr 
        }
        ret = 0;
     }
+    oldctxt->nbentities += ctxt->nbentities;
     if (sax != NULL) 
        ctxt->sax = oldsax;
     xmlFreeParserCtxt(ctxt);
_______________________________________________
xml mailing list, project page  http://xmlsoft.org/
[email protected]
http://mail.gnome.org/mailman/listinfo/xml

Reply via email to