Skip to content

Commit 1e37f8a

Browse files
author
User
committed
Strip BOM from decoded unicode content
1 parent 6a7d497 commit 1e37f8a

3 files changed

Lines changed: 40 additions & 23 deletions

File tree

tests/test_bom_encoding.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,48 @@
11
# coding: utf-8
22
import codecs
33

4-
from unicodec.bom_encoding import find_bom_encoding
4+
from unicodec.bom_encoding import BOM_ENCODINGS, find_bom_encoding
5+
from unicodec.main import decode_content
56

67

7-
def test_bom_preservation_while_decoding():
8-
# type: () -> None
8+
def test_decode_utf8_preserves_bom(): # type: () -> None
99
# fmt: off
1010
msg = codecs.BOM_UTF8 + u"привет".encode("utf-8")
1111
assert msg.decode("utf-8") == codecs.BOM_UTF8.decode("utf-8") + u"привет"
1212
# fmt: on
1313

1414

15-
def test_find_bom_encoding_empty_data():
16-
# type: () -> None
17-
assert find_bom_encoding(b"") is None
15+
def test_find_bom_encoding_empty_data(): # type: () -> None
16+
assert find_bom_encoding(b"") == (None, None)
1817

1918

20-
def test_find_bom_encoding_nonbom_data():
21-
# type: () -> None
22-
assert find_bom_encoding(b"asdf") is None
19+
def test_find_bom_encoding_nonbom_data(): # type: () -> None
20+
assert find_bom_encoding(b"asdf") == (None, None)
2321

2422

25-
def test_find_bom_encoding_utf8_bom():
26-
# type: () -> None
27-
assert find_bom_encoding(codecs.BOM_UTF8 + b"asdf") == "utf-8"
23+
def test_find_bom_encoding_utf8_bom(): # type: () -> None
24+
assert find_bom_encoding(codecs.BOM_UTF8 + b"asdf") == (codecs.BOM_UTF8, "utf-8")
2825

2926

30-
def test_find_bom_encoding_utf16_be_bom():
31-
# type: () -> None
32-
assert find_bom_encoding(codecs.BOM_UTF16_BE + b"asdf") == "utf-16-be"
27+
def test_find_bom_encoding_utf16_be_bom(): # type: () -> None
28+
assert find_bom_encoding(codecs.BOM_UTF16_BE + b"asdf") == (
29+
codecs.BOM_UTF16_BE,
30+
"utf-16-be",
31+
)
3332

3433

35-
def test_find_bom_encoding_utf32_le_bom():
36-
# type: () -> None
37-
assert find_bom_encoding(codecs.BOM_UTF32_LE + b"asdf") == "utf-32-le"
34+
def test_find_bom_encoding_utf32_le_bom(): # type: () -> None
35+
assert find_bom_encoding(codecs.BOM_UTF32_LE + b"asdf") == (
36+
codecs.BOM_UTF32_LE,
37+
"utf-32-le",
38+
)
39+
40+
41+
def test_bom_is_stripped(): # type: () -> None
42+
# fmt: off
43+
msg = u"жук"
44+
# fmt: on
45+
for bom, enc in BOM_ENCODINGS:
46+
msg_enc = bom + msg.encode(enc)
47+
msg_dec = decode_content(msg_enc)
48+
assert msg_dec == msg

unicodec/bom_encoding.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
__all__ = ["find_bom_encoding"]
44

55
# Order does matter here. UTF-32 BOMs must be preceed UTF-16 BOMs.
6+
# fmt: off
7+
BOM_UNICODE = u'\ufeff'
8+
# fmt: on
69
BOM_ENCODINGS = [
710
(codecs.BOM_UTF32_BE, "utf-32-be"),
811
(codecs.BOM_UTF32_LE, "utf-32-le"),
@@ -13,13 +16,13 @@
1316

1417

1518
def find_bom_encoding(data):
16-
# type: (bytes) -> None | str
19+
# type: (bytes) -> tuple[None, None] | tuple[bytes, str]
1720
"""Search for BOM signature and return encoding which uses such bom.
1821
1922
Return None if BOM signature not found.
2023
"""
2124
# Reference: https://encoding.spec.whatwg.org/#bom-sniff
2225
for bom_bytes, enc in BOM_ENCODINGS:
2326
if data.startswith(bom_bytes):
24-
return enc
25-
return None
27+
return bom_bytes, enc
28+
return None, None

unicodec/main.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Literal
55

66
from . import entities
7-
from .bom_encoding import find_bom_encoding
7+
from .bom_encoding import BOM_UNICODE, find_bom_encoding
88
from .errors import InvalidEncodingNameError
99
from .html_encoding import detect_html_encoding
1010
from .http_encoding import parse_content_type_header_encoding
@@ -20,7 +20,7 @@ def detect_content_encoding(
2020
markup="html", # type: Literal["html", "xml"]
2121
):
2222
# type: (...) -> str
23-
enc = find_bom_encoding(data)
23+
bom, enc = find_bom_encoding(data)
2424
if enc:
2525
try:
2626
return normalize_encoding_name(enc)
@@ -57,6 +57,9 @@ def decode_content( # pylint: disable=R0917
5757
data, content_type_header=content_type_header, markup=markup
5858
)
5959
data = data.decode(encoding)
60+
# Remove BOM, it might be at the start of decoded unicode text
61+
if data.startswith(BOM_UNICODE):
62+
data = data[len(BOM_UNICODE) :]
6063
if remove_null_bytes:
6164
data = data.replace("\x00", "")
6265
if decode_entities:

0 commit comments

Comments
 (0)