Decode UTF-16 clipboard text (#7249)
This commit is contained in:
@@ -43,7 +43,25 @@ emit_image() {
|
||||
}
|
||||
|
||||
emit_text() {
|
||||
jq -cRs 'select(length > 0) | {type:"text", text:.}'
|
||||
perl -MEncode=decode,FB_CROAK -MJSON::PP=encode_json -0777 -e '
|
||||
my $raw = <STDIN>;
|
||||
exit unless length $raw;
|
||||
|
||||
my $encoding;
|
||||
if ($raw =~ /^(?:\xFF\xFE|\xFE\xFF)/) {
|
||||
$encoding = "UTF-16";
|
||||
} elsif ($raw =~ /^(?:[^\0]\0)+\z/s) {
|
||||
# BOM-less UTF-16 is indistinguishable from NUL-separated bytes, so only
|
||||
# decode the consistent whole-payload pattern seen from affected apps.
|
||||
$encoding = "UTF-16LE";
|
||||
} elsif ($raw =~ /^(?:\0[^\0])+\z/s) {
|
||||
$encoding = "UTF-16BE";
|
||||
}
|
||||
|
||||
my $text = $encoding ? eval { decode($encoding, $raw, FB_CROAK) } : undef;
|
||||
$text = decode("UTF-8", $raw) unless defined $text;
|
||||
print "{\"type\":\"text\",\"text\":", encode_json($text), "}\n";
|
||||
'
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
|
||||
@@ -292,6 +292,47 @@ capture_output=$(printf 'closing app copy' | WL_PASTE_TEXT="stale read" XDG_RUNT
|
||||
[[ $capture_output == '{"type":"text","text":"closing app copy"}' ]] || fail "clipboard capture records watched text from stdin"
|
||||
pass "clipboard capture records watched text from stdin"
|
||||
|
||||
capture_output=$(printf '%s' 'UTF-16 clipboard - fixed' | iconv -f UTF-8 -t UTF-16LE | XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text)
|
||||
[[ $capture_output == '{"type":"text","text":"UTF-16 clipboard - fixed"}' ]] || fail "clipboard capture decodes UTF-16LE text"
|
||||
pass "clipboard capture decodes UTF-16LE text"
|
||||
|
||||
# BOM-less UTF-16LE "A" is byte-identical to UTF-8 "A\0". The strict
|
||||
# whole-payload pattern intentionally resolves that ambiguity as UTF-16.
|
||||
capture_output=$(printf 'A' | iconv -f UTF-8 -t UTF-16LE | XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text)
|
||||
[[ $capture_output == '{"type":"text","text":"A"}' ]] || fail "clipboard capture decodes exact NUL-padded UTF-16LE text"
|
||||
pass "clipboard capture decodes exact NUL-padded UTF-16LE text"
|
||||
|
||||
capture_output=$(printf 'BE text' | iconv -f UTF-8 -t UTF-16BE | XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text)
|
||||
[[ $capture_output == '{"type":"text","text":"BE text"}' ]] || fail "clipboard capture decodes exact NUL-padded UTF-16BE text"
|
||||
pass "clipboard capture decodes exact NUL-padded UTF-16BE text"
|
||||
|
||||
capture_output=$({ printf '\377\376'; printf '%s' 'Little endian 日本 😀' | iconv -f UTF-8 -t UTF-16LE; } | XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text)
|
||||
[[ $capture_output == '{"type":"text","text":"Little endian 日本 😀"}' ]] || fail "clipboard capture decodes BOM-tagged UTF-16LE text"
|
||||
pass "clipboard capture decodes BOM-tagged UTF-16LE text"
|
||||
|
||||
capture_output=$({ printf '\376\377'; printf '%s' 'Big endian 日本 😀' | iconv -f UTF-8 -t UTF-16BE; } | XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text)
|
||||
[[ $capture_output == '{"type":"text","text":"Big endian 日本 😀"}' ]] || fail "clipboard capture decodes BOM-tagged UTF-16BE text"
|
||||
pass "clipboard capture decodes BOM-tagged UTF-16BE text"
|
||||
|
||||
assert_ambiguous_utf16_falls_back() {
|
||||
local description="$1" value="$2" expected capture_output
|
||||
printf '%s' "$value" | iconv -f UTF-8 -t UTF-16LE >"$TMPDIR/ambiguous-utf16"
|
||||
expected=$(jq -cRs '{type:"text", text:.}' <"$TMPDIR/ambiguous-utf16")
|
||||
capture_output=$(XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text <"$TMPDIR/ambiguous-utf16")
|
||||
[[ $capture_output == "$expected" ]] || fail "$description" "expected: $expected\nactual: $capture_output"
|
||||
pass "$description"
|
||||
}
|
||||
|
||||
assert_ambiguous_utf16_falls_back "clipboard capture leaves BOM-less UTF-16 punctuation undecoded" '—'
|
||||
assert_ambiguous_utf16_falls_back "clipboard capture leaves BOM-less UTF-16 CJK undecoded" '日本'
|
||||
assert_ambiguous_utf16_falls_back "clipboard capture leaves BOM-less UTF-16 surrogate pairs undecoded" '😀'
|
||||
|
||||
printf '\377\376\075\330' >"$TMPDIR/malformed-utf16"
|
||||
expected=$(jq -cRs '{type:"text", text:.}' <"$TMPDIR/malformed-utf16")
|
||||
capture_output=$(XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" text <"$TMPDIR/malformed-utf16")
|
||||
[[ $capture_output == "$expected" ]] || fail "clipboard capture falls back from malformed UTF-16" "expected: $expected\nactual: $capture_output"
|
||||
pass "clipboard capture falls back from malformed UTF-16"
|
||||
|
||||
capture_output=$(printf 'png-data' | XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh" image/png)
|
||||
image_path=$(jq -r '.path' <<<"$capture_output")
|
||||
jq -e '.type == "image" and .mime == "image/png" and (.capturedAt | type == "string")' <<<"$capture_output" >/dev/null || fail "clipboard capture records watched png images"
|
||||
|
||||
Reference in New Issue
Block a user