Warum ersetzt Python-Dekodierung mehr als die ungültigen Bytes einer kodierten Zeichenfolge?

Der Versuch, eine ungültige verschlüsselte utf-8-HTML-Seite zu dekodieren, führt zu unterschiedlichen Ergebnissen in Python, Firefox und Chrome.

Das ungültig codierte Fragment von der Testseite sieht aus wie'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

AKTUALISIEREN: Diese Frage schloss in einemFehlerbericht zu Python Unicode-Komponente. Es wird berichtet, dass das Problem in Python 2.7.11 und 3.5.2 behoben ist.

Im Folgenden werden die Ersetzungsrichtlinien beschrieben, die zur Behandlung von Decodierungsfehlern in Python, Firefox und Chrome verwendet werden. Beachten Sie, wie sie sich unterscheiden und wie Python Built-In das Gültige entferntS (plus die ungültige Folge von Bytes).

Python

Das eingebautereplace Fehlerbehandlungsroutine ersetzt die ungültige\xe3\xab plus dieS vonSUFFIX von U + FFFD

>>> fragment.decode('utf-8', 'replace')
u'PREFIX\ufffdUFFIX'
>>> print _
PREFIX�UFFIX
Browser

Um zu testen, wie Browser die ungültige Folge von Bytes dekodieren, wird ein CGI-Skript verwendet:

#!/usr/bin/env python
print """\
Content-Type: text/plain; charset=utf-8

PREFIX\xe3\xabSUFFIX"""

Gerenderte Firefox- und Chrome-Browser:

PREFIX�SUFFIX
Warum eingebautreplace Fehlerbehandlungsroutine fürstr.decode entfernt dieS vonSUFFIX

(War UPDATE 1)

Laut WikipediaUTF-8 (danke mjv), die folgenden Bereiche von Bytes werden verwendet, um den Beginn einer Sequenz von Bytes anzuzeigen

0xC2-0xDF: Beginn der 2-Byte-Sequenz0xE0-0xEF: Start der 3-Byte-Sequenz0xF0-0xF4: Start der 4-Byte-Sequenz

'PREFIX\xe3\abSUFFIX' Testfragment hat0xE3Weist der Python-Decoder an, dass eine 3-Byte-Sequenz folgt, die Sequenz für ungültig befunden wird und der Python-Decoder die gesamte Sequenz einschließlich ignoriert'\xabS', und wird fortgesetzt, nachdem alle möglichen korrekten Sequenzen ignoriert wurden, die in der Mitte beginnen.

Dies bedeutet, dass für eine ungültige codierte Sequenz wie'\xF0SUFFIX'wird es dekodierenu'\ufffdFIX' Anstatt vonu'\ufffdSUFFIX'.

Beispiel 1: Einführung von DOM-Parsing-Fehlern

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

Beispiel 2: Sicherheitsprobleme (Siehe auchÜberlegungen zur Unicode-Sicherheit):

>>> '\xf0<!-- <script>alert("hi!");</script> -->'.decode('utf-8', 'replace')
u'\ufffd- <script>alert("hi!");</script> -->'
>>> print _
�- <script>alert("hi!");</script> -->

Beispiel 3: Entfernen Sie gültige Informationen für eine Scraping-Anwendung

>>> '\xf0' + u'it\u2019s'.encode('utf-8') # "it’s"
'\xf0it\xe2\x80\x99s'
>>> _.decode('utf-8', 'replace')
u'\ufffd\ufffd\ufffds'
>>> print _
���s

Verwenden eines CGI-Skripts zum Rendern in Browsern:

#!/usr/bin/env python
print """\
Content-Type: text/plain; charset=utf-8

\xf0it\xe2\x80\x99s"""

Gerendert:

�it’s
Gibt es eine offizielle empfohlene Methode für den Umgang mit dem Decodieren von Ersatzteilen?

(War UPDATE 2)

In einemöffentliche Überprüfunghat sich das Unicode Technical Committee für Option 2 der folgenden Kandidaten entschieden:

Ersetzen Sie die gesamte schlecht geformte Teilsequenz durch eine einzelne U + FFFD.Ersetzen Sie jeden maximalen Teil der schlecht geformten Teilsequenz durch eine einzelne U + FFFD.Ersetzen Sie jede Codeeinheit der schlecht geformten Teilsequenz durch eine einzelne U + FFFD.

UTC-Auflösung war am 2008-08-29, Quelle:http://www.unicode.org/review/resolved-pri-100.html

UTC Public Review 121 enthält auch einen ungültigen Bytestream als Beispiel'\x61\xF1\x80\x80\xE1\x80\xC2\x62'Zeigt die Dekodierungsergebnisse für jede Option an.

            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

In Python sind die drei Ergebnisse:

u'a\ufffdb' zeigt alsa�bu'a\ufffd\ufffd\ufffdb' zeigt alsa���bu'a\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdb' zeigt alsa������b

Und hier ist, was Python für das ungültige Beispiel bytestream tut:

>>> '\x61\xF1\x80\x80\xE1\x80\xC2\x62'.decode('utf-8', 'replace')
u'a\ufffd\ufffd\ufffd'
>>> print _
a���

Verwenden Sie erneut ein CGI-Skript, um zu testen, wie Browser die fehlerhaft codierten Bytes rendern:

#!/usr/bin/env python
print """\
Content-Type: text/plain; charset=utf-8

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

Sowohl Chrome als auch Firefox wurden gerendert:

a���b

Beachten Sie, dass das vom Browser wiedergegebene Ergebnis mit Option 2 der Empfehlung PR121 übereinstimmt

WährendOption 3 sieht einfach in Python umsetzbar aus,Option 2 und 1 sind eine Herausforderung.

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

Antworten auf die Frage(4)

Ihre Antwort auf die Frage