web-dev-qa-db-fra.com

Traitement des caractères hexadécimaux XML invalides

J'essaie d'envoyer un document XML sur le réseau, mais je reçois l'exception suivante:

"MY LONG EMAIL STRING" was specified for the 'Body' element. ---> System.ArgumentException: '', hexadecimal value 0x02, is an invalid character.
   at System.Xml.XmlUtf8RawTextWriter.InvalidXmlChar(Int32 ch, Byte* pDst, Boolean entitize)
   at System.Xml.XmlUtf8RawTextWriter.WriteElementTextBlock(Char* pSrc, Char* pSrcEnd)
   at System.Xml.XmlUtf8RawTextWriter.WriteString(String text)
   at System.Xml.XmlUtf8RawTextWriterIndent.WriteString(String text)
   at System.Xml.XmlRawWriter.WriteValue(String value)
   at System.Xml.XmlWellFormedWriter.WriteValue(String value)
   at Microsoft.Exchange.WebServices.Data.EwsServiceXmlWriter.WriteValue(String value, String name)
   --- End of inner exception stack trace ---

Je n'ai aucun contrôle sur ce que je tente d'envoyer car la chaîne est collectée à partir d'un email. Comment puis-je encoder ma chaîne pour qu'elle soit valide en XML tout en conservant les caractères illégaux?

J'aimerais conserver les personnages originaux d'une manière ou d'une autre.

13
gcso
byte[] toEncodeAsBytes
            = System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);
      string returnValue
            = System.Convert.ToBase64String(toEncodeAsBytes);

est une façon de faire cela

14
parapura rajkumar

Le code suivant supprime les caractères XML non valides d'une chaîne et renvoie une nouvelle chaîne sans eux:

public static string CleanInvalidXmlChars(string text) 
{ 
     // From xml spec valid chars: 
     // #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]     
     // any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. 
     string re = @"[^\x09\x0A\x0D\x20-\xD7FF\xE000-\xFFFD\x10000-x10FFFF]"; 
     return Regex.Replace(text, re, ""); 
}
20
mathifonseca

La solution suivante supprime tous les caractères XML non valides, mais elle me semble aussi performante que possible, et en particulier pasallouer un nouveau StringBuilder ainsi qu'une nouvelle chaîne, sauf si c'est le cas Il a déjà déterminé que la chaîne contenait des caractères non valides. Ainsi, le point chaud finit par n'être qu'une boucle for for unique sur les caractères, la vérification finissant souvent par ne pas être supérieure à deux comparaisons numériques supérieures à/inférieures à chaque caractère Si aucune d'entre elles n'est trouvée, la chaîne d'origine est simplement renvoyée. Ceci est particulièrement utile lorsque la grande majorité des chaînes sont correctes au début, il est agréable de les avoir aussi vite que possible (sans allocation, etc.).

-- mettre à jour --

Voyez ci-dessous comment on peut également écrire directement un XElement contenant ces caractères non valides, même s’il utilise ce code -

Une partie de ce code a été influencée par par la solution de M. Tom Bogle ici . Voir aussi sur ce même fil les informations utiles contenues dans le message de superlogical . Cependant, tous ces éléments instancient toujours un nouveau StringBuilder et une chaîne.

USAGE: 

    string xmlStrBack = XML.ToValidXmlCharactersString("any string");

TESTER:

    public static void TestXmlCleanser()
    {
        string badString = "My name is Inigo Montoya"; // you may not see it, but bad char is in 'MontXoya'
        string goodString = "My name is Inigo Montoya!";

        string back1 = XML.ToValidXmlCharactersString(badString); // fixes it
        string back2 = XML.ToValidXmlCharactersString(goodString); // returns same string

        XElement x1 = new XElement("test", back1);
        XElement x2 = new XElement("test", back2);
        XElement x3WithBadString = new XElement("test", badString);

        string xml1 = x1.ToString();
        string xml2 = x2.ToString().Print();

        string xmlShouldFail = x3WithBadString.ToString();
    }

// --- CODE --- (j'ai ces méthodes dans une classe d'utilitaire statique appelée XML)

    /// <summary>
    /// Determines if any invalid XML 1.0 characters exist within the string,
    /// and if so it returns a new string with the invalid chars removed, else 
    /// the same string is returned (with no wasted StringBuilder allocated, etc).
    /// </summary>
    /// <param name="s">Xml string.</param>
    /// <param name="startIndex">The index to begin checking at.</param>
    public static string ToValidXmlCharactersString(string s, int startIndex = 0)
    {
        int firstInvalidChar = IndexOfFirstInvalidXMLChar(s, startIndex);
        if (firstInvalidChar < 0)
            return s;

        startIndex = firstInvalidChar;

        int len = s.Length;
        var sb = new StringBuilder(len);

        if (startIndex > 0)
            sb.Append(s, 0, startIndex);

        for (int i = startIndex; i < len; i++)
            if (IsLegalXmlChar(s[i]))
                sb.Append(s[i]);

        return sb.ToString();
    }

    /// <summary>
    /// Gets the index of the first invalid XML 1.0 character in this string, else returns -1.
    /// </summary>
    /// <param name="s">Xml string.</param>
    /// <param name="startIndex">Start index.</param>
    public static int IndexOfFirstInvalidXMLChar(string s, int startIndex = 0)
    {
        if (s != null && s.Length > 0 && startIndex < s.Length) {

            if (startIndex < 0) startIndex = 0;
            int len = s.Length;

            for (int i = startIndex; i < len; i++)
                if (!IsLegalXmlChar(s[i]))
                    return i;
        }
        return -1;
    }

    /// <summary>
    /// Indicates whether a given character is valid according to the XML 1.0 spec.
    /// This code represents an optimized version of Tom Bogle's on SO: 
    /// https://stackoverflow.com/a/13039301/264031.
    /// </summary>
    public static bool IsLegalXmlChar(char c)
    {
        if (c > 31 && c <= 55295)
            return true;
        if (c < 32)
            return c == 9 || c == 10 || c == 13;
        return (c >= 57344 && c <= 65533) || c > 65535;
        // final comparison is useful only for integral comparison, if char c -> int c, useful for utf-32 I suppose
        //c <= 1114111 */ // impossible to get a code point bigger than 1114111 because Char.ConvertToUtf32 would have thrown an exception
    }

======== ================= 

Écrit XElement.ToString directement

======== ================= 

Tout d'abord, l'utilisation de cette méthode d'extension:

string result = xelem.ToStringIgnoreInvalidChars();

- Test plus complet -

    public static void TestXmlCleanser()
    {
        string badString = "My name is Inigo Montoya"; // you may not see it, but bad char is in 'MontXoya'

        XElement x = new XElement("test", badString);

        string xml1 = x.ToStringIgnoreInvalidChars();                               
        //result: <test>My name is Inigo Montoya</test>

        string xml2 = x.ToStringIgnoreInvalidChars(deleteInvalidChars: false);
        //result: <test>My name is Inigo Mont&#x1E;oya</test>
    }

--- code ---

    /// <summary>
    /// Writes this XML to string while allowing invalid XML chars to either be
    /// simply removed during the write process, or else encoded into entities, 
    /// instead of having an exception occur, as the standard XmlWriter.Create 
    /// XmlWriter does (which is the default writer used by XElement).
    /// </summary>
    /// <param name="xml">XElement.</param>
    /// <param name="deleteInvalidChars">True to have any invalid chars deleted, else they will be entity encoded.</param>
    /// <param name="indent">Indent setting.</param>
    /// <param name="indentChar">Indent char (leave null to use default)</param>
    public static string ToStringIgnoreInvalidChars(this XElement xml, bool deleteInvalidChars = true, bool indent = true, char? indentChar = null)
    {
        if (xml == null) return null;

        StringWriter swriter = new StringWriter();
        using (XmlTextWriterIgnoreInvalidChars writer = new XmlTextWriterIgnoreInvalidChars(swriter, deleteInvalidChars)) {

            // -- settings --
            // unfortunately writer.Settings cannot be set, is null, so we can't specify: bool newLineOnAttributes, bool omitXmlDeclaration
            writer.Formatting = indent ? Formatting.Indented : Formatting.None;

            if (indentChar != null)
                writer.IndentChar = (char)indentChar;

            // -- write --
            xml.WriteTo(writer); 
        }

        return swriter.ToString();
    }

- cela utilise le XmlTextWritter suivant -

public class XmlTextWriterIgnoreInvalidChars : XmlTextWriter
{
    public bool DeleteInvalidChars { get; set; }

    public XmlTextWriterIgnoreInvalidChars(TextWriter w, bool deleteInvalidChars = true) : base(w)
    {
        DeleteInvalidChars = deleteInvalidChars;
    }

    public override void WriteString(string text)
    {
        if (text != null && DeleteInvalidChars)
            text = XML.ToValidXmlCharactersString(text);
        base.WriteString(text);
    }
}
5
Nicholas Petersen

Je suis le destinataire de la solution de @ parapurarajkumar, où les caractères illégaux sont correctement chargés dans XmlDocument, mais en cassant XmlWriter lorsque j'essaie de sauvegarder la sortie.

Mon contexte

Je regarde les journaux d'exception/d'erreur du site Web en utilisant Elmah. Elmah renvoie l'état du serveur au moment de l'exception, sous la forme d'un document XML volumineux. Pour notre moteur de génération de rapports, j’imprime joliment le code XML avec XmlWriter.

Lors d'une attaque de site Web, j'ai remarqué que certains xml n'étaient pas en cours d'analyse et recevaient cette exception '.', hexadecimal value 0x00, is an invalid character..

NON-RESOLUTION: J'ai converti le document en un byte[] et l'a nettoyé de 0x00, mais il n'en a trouvé aucun.

Lorsque j'ai numérisé le document XML, j'ai trouvé ce qui suit:

...
<form>
...
<item name="SomeField">
   <value
     string="C:\boot.ini&#x0;.htm" />
 </item>
...

Il y avait l'octet nul encodé comme une entité HTML &#x0; !!!

RÉSOLUTION: Pour corriger le codage, j'ai remplacé la valeur &#x0; avant de le charger dans ma XmlDocument, car le chargement créera l'octet nul et il sera difficile de le supprimer de l'objet. Voici tout mon processus:

XmlDocument xml = new XmlDocument();
details.Xml = details.Xml.Replace("&#x0;", "[0x00]");  // in my case I wanted to see it, otherwise just replace with ""
xml.LoadXml(details.Xml);

string formattedXml = null;

// I stuff this all in a helper function, but put it in-line for this example
StringBuilder sb = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings {
    OmitXmlDeclaration = true,
    Indent = true,
    IndentChars = "\t",
    NewLineHandling = NewLineHandling.None,
};
using (XmlWriter writer = XmlWriter.Create(sb, settings)) {
    xml.Save(writer);
    formattedXml = sb.ToString();
}

LEÇON APPRISE: sanitize pour les octets illégaux utilisant l'entité html associée, si vos données entrantes sont encodées en html à l'entrée.

4
sonjz

Travaille pour moi:

XmlWriterSettings xmlWriterSettings = new XmlWriterSettings { Encoding = Encoding.UTF8, CheckCharacters = false };
4
lucas teles

La chaîne ne peut-elle pas être nettoyée avec:

System.Net.WebUtility.HtmlDecode()

?

0
Sandy

Un autre moyen de supprimer les caractères XML incorrects dans C # à l'aide de XmlConvert.IsXmlChar, méthode (Disponible depuis .NET Framework 4.0)

public static string RemoveInvalidXmlChars(string content)
{
   return new string(content.Where(ch => System.Xml.XmlConvert.IsXmlChar(ch)).ToArray());
}

.Net Fiddle - https://dotnetfiddle.net/v1TNus

Par exemple, le symbole de tabulation verticale (\ v) n'est pas valide pour XML, il est valide pour UTF-8, mais pas pour XML 1.0, et même de nombreuses bibliothèques (y compris libxml2) ne l'utilisent pas et produisent en mode silencieux un XML non valide.

0
Alex Vazhev