gpt4 book ai didi

python - 为什么 python decode 替换了编码字符串中的无效字节?

转载 作者:IT老高 更新时间:2023-10-28 22:12:37 25 4
gpt4 key购买 nike

尝试解码无效编码的 utf-8 html 页面会产生不同的结果
python ,火狐和 Chrome 。

来自测试页面的无效编码片段看起来像 'PREFIX\xe3\xabSUFFIX'

>>> fragment = 'PREFIX\xe3\xabSUFFIX'
>>> fragment.decode('utf-8', 'strict')
...
UnicodeDecodeError: 'utf8' codec can't decode bytes in position 6-8: invalid data

更新 : 这个问题在 bug report 中结束到 Python unicode 组件。据报道,该问题已在 Python 2.7.11 和 3.5.2 中修复。

下面是用于处理解码错误的替换策略
Python、Firefox 和 Chrome。请注意它们的不同之处,特别是
python 内置删除有效 S (加上无效的字节序列)。

Python

内置 replace错误处理程序替换无效 \xe3\xab加上 S来自 SUFFIX通过 U+FFFD
>>> fragment.decode('utf-8', 'replace')
u'PREFIX\ufffdUFFIX'
>>> print _
PREFIX�UFFIX

浏览器

要测试浏览器如何解码无效字节序列,将使用 cgi 脚本:
#!/usr/bin/env python
print """\
Content-Type: text/plain; charset=utf-8

PREFIX\xe3\xabSUFFIX"""

Firefox 和 Chrome 浏览器呈现:
PREFIX�SUFFIX

为什么内置 replace str.decode 的错误处理程序正在删除 S来自 SUFFIX
(是更新 1)

根据维基百科 UTF-8 (感谢 mjv),
以下字节范围用于指示序列的开始
字节
  • 0xC2-0xDF:2 字节序列的开始
  • 0xE0-0xEF : 3 字节序列的开始
  • 0xF0-0xF4:4 字节序列的开始
  • 'PREFIX\xe3\abSUFFIX'测试片段有 0xE3 ,它指示python解码器
    后面跟着一个 3 字节的序列,发现该序列无效,python
    解码器忽略整个序列,包括 '\xabS' ,并在它之后继续
    忽略从中间开始的任何可能的正确序列。

    这意味着对于像 '\xF0SUFFIX' 这样的无效编码序列, 它会
    解码 u'\ufffdFIX'而不是 u'\ufffdSUFFIX' .

    示例 1:介绍 DOM 解析错误
    >>> '<div>\xf0<div>Price: $20</div>...</div>'.decode('utf-8', 'replace')
    u'<div>\ufffdv>Price: $20</div>...</div>'
    >>> print _
    <div>�v>Price: $20</div>...</div>

    示例 2:安全问题(另见 Unicode security considerations):
    >>> '\xf0<!-- <script>alert("hi!");</script> -->'.decode('utf-8', 'replace')
    u'\ufffd- <script>alert("hi!");</script> -->'
    >>> print _
    �- <script>alert("hi!");</script> -->

    示例 3:删除抓取应用程序的有效信息
    >>> '\xf0' + u'it\u2019s'.encode('utf-8') # "it’s"
    '\xf0it\xe2\x80\x99s'
    >>> _.decode('utf-8', 'replace')
    u'\ufffd\ufffd\ufffds'
    >>> print _
    ���s

    使用 cgi 脚本在浏览器中呈现它:
    #!/usr/bin/env python
    print """\
    Content-Type: text/plain; charset=utf-8

    \xf0it\xe2\x80\x99s"""

    渲染:
    �it’s

    是否有任何官方推荐的方法来处理解码替换?

    (是更新 2)

    public review , Unicode 技术委员会选择了选项 2
    以下候选人中:
  • 用单个 U+FFFD 替换整个格式错误的子序列。
  • 用单个 U+FFFD 替换格式错误的子序列的每个最大子部分。
  • 用单个 U+FFFD 替换格式错误的子序列的每个代码单元。

  • UTC 分辨率为 2008-08-29,来源: http://www.unicode.org/review/resolved-pri-100.html

    UTC Public Review 121还包括一个无效的字节流作为示例 '\x61\xF1\x80\x80\xE1\x80\xC2\x62' , 它显示了每个的解码结果
    选项。
                61      F1      80      80      E1      80      C2      62
    1 U+0061 U+FFFD U+0062
    2 U+0061 U+FFFD U+FFFD U+FFFD U+0062
    3 U+0061 U+FFFD U+FFFD U+FFFD U+FFFD U+FFFD U+FFFD U+0062

    在普通 Python 中,三个结果是:
  • u'a\ufffdb'显示为 a�b
  • u'a\ufffd\ufffd\ufffdb'显示为 a���b
  • u'a\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdb'显示为 a������b

  • 这是python对无效示例字节流所做的:
    >>> '\x61\xF1\x80\x80\xE1\x80\xC2\x62'.decode('utf-8', 'replace')
    u'a\ufffd\ufffd\ufffd'
    >>> print _
    a���

    同样,使用 cgi 脚本来测试浏览器如何呈现错误编码的字节:
    #!/usr/bin/env python
    print """\
    Content-Type: text/plain; charset=utf-8

    \x61\xF1\x80\x80\xE1\x80\xC2\x62"""

    Chrome 和 Firefox 都呈现:
    a���b

    请注意,浏览器呈现的结果与 PR121 推荐的选项 2 匹配

    虽然选项 3 在 python 中看起来很容易实现,但选项 2 和 1 是一个挑战。
    >>> replace_option3 = lambda exc: (u'\ufffd', exc.start+1)
    >>> codecs.register_error('replace_option3', replace_option3)
    >>> '\x61\xF1\x80\x80\xE1\x80\xC2\x62'.decode('utf-8', 'replace_option3')
    u'a\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdb'
    >>> print _
    a������b

    最佳答案

    您知道您的 S 是有效的,具有前瞻性和后见之明的好处:-) 假设那里最初有一个合法的 3 字节 UTF-8 序列,并且第 3 个字节在传输中被破坏了......随着更改你提到的,你会提示一个虚假的 S 没有被替换。如果没有纠错码、 Crystal 球或 tamborine 的好处,就没有“正确”的方法可以做到这一点。 .

    更新

    正如@mjv 所说,UTC 问题是关于应该包含多少 U+FFFD。

    事实上,Python 没有使用 UTC 的 3 个选项中的任何一个。

    这是 UTC 的唯一示例:

          61      F1      80      80      E1      80      C2      62
    1 U+0061 U+FFFD U+0062
    2 U+0061 U+FFFD U+FFFD U+FFFD U+0062
    3 U+0061 U+FFFD U+FFFD U+FFFD U+FFFD U+FFFD U+FFFD U+0062

    下面是 Python 的作用:
    >>> bad = '\x61\xf1\x80\x80\xe1\x80\xc2\x62cdef'
    >>> bad.decode('utf8', 'replace')
    u'a\ufffd\ufffd\ufffdcdef'
    >>>

    为什么?

    F1 应该开始一个 4 字节的序列,但 E1 无效。一个坏的顺序,一个替换。
    从下一个字节重新开始,第 3 个 80。Bang,另一个 FFFD。
    再次从C2开始,引入了2字节序列,但是C2 62无效,所以再次bang。

    有趣的是,UTC 没有提到 Python 正在做什么(在由前导字符指示的字节数之后重新启动)。也许这在 Unicode 标准中的某处实际上是被禁止或弃用的。需要更多阅读。关注此空间。

    更新 2 休斯顿,我们有一个问题。

    === 引自 Chapter 3 of Unicode 5.2 ===

    转换过程的限制

    不将字符串中任何格式错误的代码单元子序列解释为字符的要求(参见一致性条款 C10)对转换过程具有重要影响。

    例如,此类过程可能会将 UTF-8 代码单元序列解释为 Unicode 字符
    序列。如果转换器遇到格式错误的 UTF-8 代码单元序列,
    以有效的第一个字节开始,但不以有效的后继字节继续(参见
    表 3-7), 它不得将后继字节用作格式错误的子序列的一部分
    每当这些后继字节本身构成格式良好的 UTF-8 代码的一部分时
    单元子序列
    .

    如果 UTF-8 转换过程的实现在遇到第一个错误时停止,
    不报告任何格式错误的 UTF-8 代码单元子序列的结尾,则
    要求几乎没有实际差异。但是,该要求确实引入了一个
    如果 UTF-8 转换器继续超过检测到的错误点,则存在重大约束,
    也许通过用一个或多个 U+FFFD 替换字符代替无法解释的字符,
    格式错误的 UTF-8 代码单元子序列。例如,输入 UTF-8 代码
    单元序列 <C2 41 42> ,这样的UTF-8转换过程 不得返回 <U+FFFD><U+FFFD, U+0042> ,因为这些输出中的任何一个都是将格式良好的子序列误解为格式错误的子序列的一部分的结果。这
    这样一个过程的预期返回值将改为 <U+FFFD, U+0041, U+0042> .

    对于 UTF-8 转换过程来说,消耗有效的后继字节不仅是 不符合 ,
    但也让转换器对 开放安全漏洞 .请参阅 Unicode 技术报告 #36 , “Unicode 安全注意事项”。

    === 引用结束 ===

    然后继续详细讨论,并举例说明“要发出多少 FFFD”的问题。

    在最后引用的第二段中使用他们的示例:
    >>> bad2 = "\xc2\x41\x42"
    >>> bad2.decode('utf8', 'replace')
    u'\ufffdB'
    # FAIL

    请注意,这是 'replace' 的问题。 'ignore' str.decode('utf_8') 的选项——这完全是关于省略数据,而不是关于发射了多少 U+FFFD;正确处理数据发射部分,U+FFFD 问题自然会出现,如我未引用的部分所述。

    更新 3 当前版本的 Python(包括 2.7)的 unicodedata.unidata_version 为 '5.1.0'这可能会也可能不会表明与 Unicode 相关的代码旨在符合 Unicode 5.1.0。无论如何,直到 5.2.0 才出现在 Unicode 标准中的关于 Python 正在做什么的冗长禁令。我将在 Python 跟踪器上提出问题,但不会提及 'oht'.encode('rot13') 这个词。 .

    已举报 here

    关于python - 为什么 python decode 替换了编码字符串中的无效字节?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2547262/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com