HttpClient:检测编码的正确顺序

时间:2016-03-19 21:48:58

标签: c# http encoding dotnet-httpclient

我正在使用HttpClient来获取一些文件。我把内容放入一个字节数组(字节)。现在我需要检测编码。 contenttype将是html,css,JavaScript或XML contenttype。

目前我从标题中检查字符集,然后在最终检查文件的第一部分以查找字符集元标记之前检查BOM(字节顺序标记)。 通常这很好,因为没有冲突。

但是:这个顺序是否正确(如果发生冲突)?

我正确使用的代码:

Encoding encoding;
try
{
    encoding = Encoding.GetEncoding(responseMessage.Content.Headers.ContentType.CharSet);
}
catch
{
    using (MemoryStream ms = new MemoryStream(bytes))
    {
        using (StreamReader sr = new StreamReader(ms, Encoding.Default, true))
        {
            char[] chars = new char[1024];
            sr.Read(chars, 0, 1024);
            string textDefault = new string(chars);
            if (sr.CurrentEncoding == Encoding.Default)
            {
                encoding = Global.EncodingFraContentType(textDefault);
            }
            else
            {
                encoding = sr.CurrentEncoding;
            }
        }
    }
}
responseInfo.Text = encoding.GetString(bytes);
Global.EncodingFraContentType是一个正则表达式,用于查找在XML声明或元标记中定义的字符集。

检测字符集/编码的正确顺序是什么?

3 个答案:

答案 0 :(得分:2)

正确的答案不取决于订单,而是实际上给出了正确的结果,而且这里没有完美的答案。

如果存在冲突,则服务器已向您提供错误的内容。由于它不正确,因此无法做出正确的"订购,因为没有正确的错误方法。并且,标题和嵌入的元数据可能都是错误的!

即使稍微常用的编码也可能在开始时看起来像一个BOM看起来像UTF-8或UTF-16,但仍然是你提到的内容类型的有效例子,所以如果有&#39 ;然后获得BOM。

(一个例外是如果文档编辑得太糟糕,以至于部分切换编码,这是闻所未闻的,但是那些错误的内容非常错误,没有任何实际意义)。< / p>

如果内容中不包含大于0x7F的八位字节,那么它并不重要,标题和元数据都声称它是US-ASCII,UTF-8,任何ISO-8859系列的不同示例。编码,或者这些八位字节都映射到相同代码点的任何其他编码,那么你认为它不是真的重要,因为nett结果是相同的。将其视为元数据所说的内容,因为您不需要重写它以便正确匹配。

如果没有BOM的UTF-16,很可能很快就会出现这样的情况,因为所有这些格式在U + 0000到U + 00FF范围内都有很多具有特殊含义的字符(实际上,通常是U + 0020到U + 007F)因此你会有很多范围,每隔一个字符都有一个零字节。

如果它的八位字节高于0x7F并且是有效的UTF-8,那么它几乎肯定是UTF-8。 (同样地,如果它不是UTF-8并且八位字节高于0x7F那么它几乎肯定不会被误认为是UTF-8。)

最棘手的合理常见情况是,如果你有两种不同的编码声称存在冲突,这两种编码都是单字节每字符编码,并且存在0x80-0xFF范围内的八位字节。这是您无法确定的情况。如果一个编码是另一个编码的子集(特别是当排除C1控件时),那么你可以选择超集,但这需要存储有关这些编码的知识,以及大量的工作。大部分时间我都倾向于抛出异常,当它在日志中找到时,看看我是否可以获得修复bug的来源,或者特殊情况下的来源,但是没有如果你正在处理与你可能没有关系的大量不同来源,那就行了起来。唉,这里没有完美的答案。

值得注意的是,有时标头和嵌入式元数据都会错误地彼此一致。常见的情况是CP-1252中的内容,但声称属于ISO-8859-1。

答案 1 :(得分:1)

根据W3C Faq

  

如果文件开头有UTF-8字节顺序标记(BOM),则Internet Explorer 10或11以外的最新浏览器版本将使用它来确定页面的编码是UTF-8。它具有比任何其他声明更高的优先级,包括HTTP标头。

当谈到http-header vs meta BOM优先时,只要meta在第一个1024之内它就可以优先,尽管没有严格的规则。

答案 2 :(得分:0)

结论-重要性顺序:

  1. 字节顺序标记(BOM):如果存在,则为 AUTHORATIVE ,因为它是 由实际保存文件的编辑器添加的(只能是 以unicode编码显示)。
  2. Content-Type字符集(在服务器设置的标头中):对于动态创建/处理的文件,应该存在该文件(因为 服务器知道),但可能不适用于静态文件(服务器只是 发送那些。)
  3. 内联字符集:对于xml,html和css,可以在文档中的xml prologhtml meta tag中指定编码 或@charset in css。要阅读,您需要先解码 部分文档,例如使用“ Windows-1252”编码
  4. 假设utf-8 。这是standard of the web,今天是最常用的。
  5. 如果找到的编码等于“ ISO-8859-1”,请改用“ Windows-1252”(在html5中是必需的-请在Wikipedia中阅读更多内容

现在尝试使用找到的编码对文档进行解码。如果error handling打开,则可能会失败!在这种情况下:

  1. 使用“ Windows-1252” 。这是旧版Windows文件中的标准,并且在上次尝试时效果很好(仍然有很多旧文件)。 这将never throw个错误。但是,这当然是错误的。

我制作了一个实现此目的的方法。我使用的regex能够找到指定为的编码:

Xml <?xml version="1.0" encoding="utf-8"?><?xml encoding="utf-8"?>

html <meta charset="utf-8" /><meta http-equiv="Content-Type" content="text/html; charset=utf-8">

css @charset "utf-8";

(它适用于单qout和双qoutes)。

您将需要:

using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

这是返回解码后的字符串的方法(参数为HttpClientUri):

public static async Task<string> GetString(HttpClient httpClient, Uri url)
{
    byte[] bytes;
    Encoding encoding = null;
    Regex charsetRegex = new Regex(@"(?<=(<meta.*?charset=|^\<\?xml.*?encoding=|^@charset[ ]?)[""']?)[\w-]+?(?=[""';\r\n])",
        RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);

    using (HttpResponseMessage responseMessage = await httpClient.GetAsync(url).ConfigureAwait(false))
    {
        responseMessage.EnsureSuccessStatusCode();
        bytes = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
        string headerCharset = responseMessage?.Content?.Headers?.ContentType?.CharSet;

        byte[] buffer = new byte[0x1000];
        Array.Copy(bytes, buffer, Math.Min(bytes.Length, buffer.Length));
        using (MemoryStream ms = new MemoryStream(buffer))
        {
            using (StreamReader sr = new StreamReader(ms, Encoding.GetEncoding("Windows-1252"), true, buffer.Length, true))
            {
                string testString = await sr.ReadToEndAsync().ConfigureAwait(false);
                if (!sr.CurrentEncoding.Equals(Encoding.GetEncoding("Windows-1252")))
                {
                    encoding = sr.CurrentEncoding;
                }
                else if (headerCharset != null)
                {
                    encoding = Encoding.GetEncoding(headerCharset, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
                }
                else
                {
                    string inlineCharset = charsetRegex.Match(testString).Value;
                    if (!string.IsNullOrEmpty(inlineCharset))
                    {
                        encoding = Encoding.GetEncoding(inlineCharset, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
                    }
                    else
                    {
                        encoding = new UTF8Encoding(false, true);
                    }
                }
                if (encoding.Equals(Encoding.GetEncoding("iso-8859-1")))
                {
                    encoding = Encoding.GetEncoding("Windows-1252", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
                }
            }
        }
        using (MemoryStream ms = new MemoryStream(bytes))
        {
            try
            {
                using (StreamReader sr = new StreamReader(ms, encoding, false, 0x8000, true))
                {
                    return await sr.ReadToEndAsync().ConfigureAwait(false);
                }
            }
            catch (DecoderFallbackException)
            {
                ms.Position = 0;
                using (StreamReader sr = new StreamReader(ms, Encoding.GetEncoding("Windows-1252"), false, 0x8000, true))
                {
                    return await sr.ReadToEndAsync().ConfigureAwait(false);
                }
            }
        }
    }
}

您应该将方法调用包装在try / catch中,因为如果请求失败,HttpClient可能会引发错误。

更新

.Net Core中,您没有使用'Windows-1252'编码(恕我直言,这是个大错误),因此在这里您必须使用'ISO-8859-1'。