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).
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
BrowserUm 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�b
u'a\ufffd\ufffd\ufffdb'
zeigt alsa���b
u'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