Decode mixed UTF-16 clipboard text (#7466)

* Decode mixed UTF-16 clipboard text

* Harden UTF-16 clipboard detection

* Finish UTF-16 decoder hardening
This commit is contained in:
David Heinemeier Hansson
2026-08-19 11:56:51 +02:00
committed by GitHub
parent fa955bfa9d
commit 238021cd67
2 changed files with 81 additions and 10 deletions
+33 -8
View File
@@ -43,22 +43,47 @@ emit_image() {
}
emit_text() {
perl -MEncode=decode,FB_CROAK -MJSON::PP=encode_json -0777 -e '
perl -MEncode=decode,FB_CROAK,LEAVE_SRC -MJSON::PP=encode_json -0777 -e '
my $raw = <STDIN>;
exit unless length $raw;
my $encoding;
my $heuristic_encoding = 0;
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";
} elsif (length($raw) % 2 == 0 && index($raw, "\0") >= 0) {
my $units = length($raw) / 2;
my $nuls = $raw =~ tr/\0/\0/;
# Neither byte lane can reach the padding threshold when the entire
# payload contains fewer NULs than that, so avoid two full string passes.
if ($nuls * 4 >= $units * 3) {
my $even_bytes = $raw;
$even_bytes =~ s/(.)./$1/sg;
my $even_nuls = $even_bytes =~ tr/\0/\0/;
undef $even_bytes;
my $odd_bytes = $raw;
$odd_bytes =~ s/.(.)/$1/sg;
my $odd_nuls = $odd_bytes =~ tr/\0/\0/;
# BOM-less UTF-16 is indistinguishable from NUL-separated bytes. Decode
# only when at least three quarters of the code units have consistent
# padding and fewer than one quarter have NULs in the opposite byte.
if ($odd_nuls * 4 >= $units * 3 && $even_nuls * 4 < $units) {
$encoding = "UTF-16LE";
$heuristic_encoding = 1;
} elsif ($even_nuls * 4 >= $units * 3 && $odd_nuls * 4 < $units) {
$encoding = "UTF-16BE";
$heuristic_encoding = 1;
}
}
}
my $text = $encoding ? eval { decode($encoding, $raw, FB_CROAK) } : undef;
my $text = $encoding ? eval { decode($encoding, $raw, FB_CROAK | LEAVE_SRC) } : undef;
if ($heuristic_encoding && defined($text) && $text =~ /[\x00-\x08\x0E-\x1A\x1C-\x1F]/) {
$text = undef;
}
$text = decode("UTF-8", $raw) unless defined $text;
print "{\"type\":\"text\",\"text\":", encode_json($text), "}\n";
'