Почему декодирование python заменяет больше, чем недопустимые байты из закодированной строки?

Попытка декодировать неверно закодированную HTML-страницу utf-8 дает разные результаты в python, firefox и 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

ОБНОВИТЬ: Этот вопрос заключен всообщение об ошибке в компонент Python Unicode. Сообщается, что проблема исправлена ​​в Python 2.7.11 и 3.5.2.

Далее следует политика замены, используемая для обработки ошибок декодирования в Python, Firefox и Chrome. Обратите внимание, чем они отличаются, и, в частности, как встроенная функция Python удаляет действительныеS (плюс неверная последовательность байтов).

питон

Встроенный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):

>>> '\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)

Вобщественный обзорТехнический комитет по Unicode выбрал вариант 2 из следующих кандидатов:

Замените всю плохо сформированную подпоследовательность одним U + FFFD.Замените каждую максимальную подпоследовательность плохо сформированной подпоследовательности одним U + FFFD.Замените каждую единицу кода плохо сформированной подпоследовательности одним U + FFFD.

Резолюция UTC была в 2008-08-29, источник:http://www.unicode.org/review/resolved-pri-100.html

Публичное обозрение UTC 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�bu'a\ufffd\ufffd\ufffdb' показывает какa���bu'a\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdb' показывает какa������b

А вот что Python делает для недопустимого примера bytestream:

>>> '\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

Обратите внимание, что результат отображения браузерами соответствует варианту 2 рекомендации PR121.

В то время каквариант 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

Ответы на вопрос(4)

Ваш ответ на вопрос