Skip to content

Commit

Permalink
fix: handle errors when decoding bytes from subprocesses (#222)
Browse files Browse the repository at this point in the history
Since these bytes come from the output of unknown processes, there is no
guarantee that they are valid utf-8 text. We also have no way to know
what encoding they have (if any), so the best we can do is replace the
invalid bytes with the usual unicode marker for that.

Fixes #221
  • Loading branch information
tigarmo authored Jan 18, 2024
1 parent 8b61db8 commit 9e017d4
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 2 deletions.
5 changes: 3 additions & 2 deletions craft_cli/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,9 @@ def _write(self, data: bytes) -> None:
useful_line = data[pointer:newline_position]
pointer = newline_position + 1

# write the useful line to intended outputs
unicode_line = useful_line.decode("utf8")
# write the useful line to intended outputs. Decode with errors="replace"
# here because we don't know where this line is coming from.
unicode_line = useful_line.decode("utf8", errors="replace")
# replace tabs with a set number of spaces so that the printer
# can correctly count the characters.
unicode_line = unicode_line.replace("\t", " ")
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/test_messages_stream_cm.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,24 @@ def test_pipereader_tabs(recording_printer, stream):
assert msg.text == ":: 123 456" # tabs expanded into 2 spaces


@pytest.mark.parametrize(
("invalid_text", "expected"),
[(b"\xf0\x28\x8c\xbc", "�(��"), (b"\xf0\x90\x28\xbc", "�(�"), (b"\xf0\x90\x8c\x28", "�(")],
)
def test_pipereader_invalid_utf8(recording_printer, invalid_text, expected):
"""Check that bytes that aren't valid utf-8 text don't crash."""
invalid_bytes = b"valid prefix " + invalid_text + b" valid suffix\n"

flags = {"use_timestamp": False, "ephemeral": False, "end_line": True}
prt = _PipeReaderThread(recording_printer, sys.stdout, flags)
prt.start()
os.write(prt.write_pipe, invalid_bytes)
prt.stop()

(msg,) = recording_printer.written_terminal_lines
assert msg.text == f":: valid prefix {expected} valid suffix"


def test_pipereader_chunk_assembler(recording_printer, monkeypatch):
"""Converts ok arbitrary chunks to lines."""
monkeypatch.setattr(messages, "_PIPE_READER_CHUNK_SIZE", 5)
Expand Down

0 comments on commit 9e017d4

Please sign in to comment.