Попытка декодирования неверной кодированной страницы utf-8 html дает разные результаты в 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. Сообщается, что проблема исправлена в 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 script:
#!/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)
Согласно wikipedia 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 script для рендеринга в браузерах:
#!/usr/bin/env python
print """\
Content-Type: text/plain; charset=utf-8
\xf0it\xe2\x80\x99s"""
Рендеринг:
�it’s
Есть ли какой-либо официальный рекомендуемый способ обработки замеров декодирования?
(было ОБНОВЛЕНИЕ 2)
В публичный отзыв, Технический комитет Юникода выбрал вариант 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 для недопустимого примера bytestream:
>>> '\x61\xF1\x80\x80\xE1\x80\xC2\x62'.decode('utf-8', 'replace')
u'a\ufffd\ufffd\ufffd'
>>> print _
a���
Опять же, используя cgi script, чтобы проверить, как браузеры отображают багги-закодированные байты:
#!/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