Switch stage-3 triage from NUL sniff to UTF-8 validity
The NUL-byte heuristic misfiled any non-ASCII UTF-8 text (accents, CJK, emoji) as binary and let non-NUL control bytes through as text. is_binary now calls a file text when its 8000-byte sniff window is valid UTF-8 with no control bytes outside the text-safe set (tab/newline/CR/ESC/etc). - trim_truncated_utf8 drops a multi-byte char split by the window edge so it isn't mistaken for malformed bytes. - NUL still classifies as binary (valid UTF-8 scalar, non-text control). - Expanded tests: Unicode, ANSI logs, stray control byte, malformed UTF-8, boundary-split char; updated README stage-3 description.
This commit is contained in:
@@ -141,24 +141,54 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
@testset "is_binary: NUL-byte sniff" begin
|
||||
@testset "is_binary: UTF-8 sniff" begin
|
||||
mktempdir() do root
|
||||
# Plain text → text.
|
||||
# Plain ASCII text → text.
|
||||
txt = joinpath(root, "notes.txt")
|
||||
write(txt, "hello, world\nsecond line\n")
|
||||
@test is_binary(txt) == false
|
||||
|
||||
# A NUL byte anywhere in the sniff window → binary.
|
||||
# Non-ASCII UTF-8 (accents, CJK, emoji) is valid text — the whole
|
||||
# point of moving off the printable-ASCII/NUL heuristic.
|
||||
uni = joinpath(root, "unicode.txt")
|
||||
write(uni, "café — 日本語 — 🚀\n")
|
||||
@test is_binary(uni) == false
|
||||
|
||||
# ANSI-colored log: ESC + other text control bytes are text-safe.
|
||||
ansi = joinpath(root, "colored.log")
|
||||
write(ansi, "\e[31merror\e[0m: tab\there\r\nnext\n")
|
||||
@test is_binary(ansi) == false
|
||||
|
||||
# A NUL byte anywhere in the sniff window → binary (it's a control
|
||||
# byte outside the text-safe set, even though it's valid UTF-8).
|
||||
bin = joinpath(root, "blob.dat")
|
||||
write(bin, UInt8[0x01, 0x02, 0x00, 0x03])
|
||||
@test is_binary(bin) == true
|
||||
|
||||
# Empty file has no NUL → treated as text.
|
||||
# A non-NUL, non-text control byte (e.g. 0x07 BEL) → binary.
|
||||
ctrl = joinpath(root, "ctrl.dat")
|
||||
write(ctrl, UInt8[UInt8('h'), UInt8('i'), 0x07])
|
||||
@test is_binary(ctrl) == true
|
||||
|
||||
# Malformed UTF-8 (lone continuation / bad lead byte) → binary.
|
||||
bad = joinpath(root, "bad.dat")
|
||||
write(bad, UInt8[UInt8('a'), 0xff, 0xfe, 0xc3, 0x28])
|
||||
@test is_binary(bad) == true
|
||||
|
||||
# A multi-byte char split by the sniff boundary must NOT read as
|
||||
# binary: pad to one byte short of the window, then a 2-byte 'é'
|
||||
# (0xc3 0xa9) so only its lead byte lands inside the window.
|
||||
split = joinpath(root, "split.txt")
|
||||
write(split, vcat(fill(UInt8('a'), FileServer.CONTENT_SNIFF_BYTES - 1),
|
||||
UInt8[0xc3, 0xa9]))
|
||||
@test is_binary(split) == false
|
||||
|
||||
# Empty file → treated as text.
|
||||
empty = joinpath(root, "empty")
|
||||
write(empty, UInt8[])
|
||||
@test is_binary(empty) == false
|
||||
|
||||
# A NUL past the sniff window is not seen → still text.
|
||||
# Binary garbage past the sniff window is not seen → still text.
|
||||
far = joinpath(root, "far.txt")
|
||||
write(far, vcat(fill(UInt8('a'), FileServer.CONTENT_SNIFF_BYTES), UInt8[0x00]))
|
||||
@test is_binary(far) == false
|
||||
|
||||
Reference in New Issue
Block a user