]> TLD Linux GIT Repositories - packages/spamassassin.git/blob - spamassassin-3.4.1-netdns.patch
- from PLD
[packages/spamassassin.git] / spamassassin-3.4.1-netdns.patch
1 commit e064e2844aede6026433fa9635d4181b3de396aa
2 Author: Mark Martinec <mmartinec@apache.org>
3 Date:   Mon Jul 20 18:23:18 2015 +0000
4
5     Bug 7223: Net::DNS 1.01 breaks DnsResolver
6     
7     git-svn-id: https://svn.apache.org/repos/asf/spamassassin/trunk@1691991 13f79535-47bb-0310-9956-ffa450edef68
8
9 diff --git a/lib/Mail/SpamAssassin/DnsResolver.pm b/lib/Mail/SpamAssassin/DnsResolver.pm
10 index ce51bee83..612245cac 100644
11 --- a/lib/Mail/SpamAssassin/DnsResolver.pm
12 +++ b/lib/Mail/SpamAssassin/DnsResolver.pm
13 @@ -581,7 +581,7 @@ sub new_dns_packet {
14      #    time, $domain, $type, $packet->id);
15      1;
16    } or do {
17 -    # this can if a domain name in a query is invalid, or if a timeout signal
18 +    # get here if a domain name in a query is invalid, or if a timeout signal
19      # happened to be trapped by this eval, or if Net::DNS signalled an error
20      my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
21      # resignal if alarm went off
22 @@ -592,6 +592,9 @@ sub new_dns_packet {
23    };
24  
25    if ($packet) {
26 +    # RD flag needs to be set explicitly since Net::DNS 1.01, Bug 7223 
27 +    $packet->header->rd(1);
28 +
29    # my $udp_payload_size = $self->{res}->udppacketsize;
30      my $udp_payload_size = $self->{conf}->{dns_options}->{edns};
31      if ($udp_payload_size && $udp_payload_size > 512) {
32 @@ -861,7 +864,8 @@ Emulates C<Net::DNS::Resolver::send()>.
33  This subroutine is a simple synchronous leftover from SpamAssassin version
34  3.3 and does not participate in packet query caching and callback grouping
35  as implemented by AsyncLoop::bgsend_and_start_lookup().  As such it should
36 -be avoided for mainstream usage.
37 +be avoided for mainstream usage.  Currently used through Mail::SPF::Server
38 +by the SPF plugin.
39  
40  =cut
41  
42 commit 41f4c5ac8f275593a2bad1cb614b5185172ef568
43 Author: Mark Martinec <mmartinec@apache.org>
44 Date:   Tue Aug 4 23:10:16 2015 +0000
45
46     Bug 7231: Net::DNS 1.01 returns answers formatted differently, breaks SA
47     
48     git-svn-id: https://svn.apache.org/repos/asf/spamassassin/trunk@1694122 13f79535-47bb-0310-9956-ffa450edef68
49
50 diff --git a/lib/Mail/SpamAssassin/Dns.pm b/lib/Mail/SpamAssassin/Dns.pm
51 index 55e1640f8..1c305c939 100644
52 --- a/lib/Mail/SpamAssassin/Dns.pm
53 +++ b/lib/Mail/SpamAssassin/Dns.pm
54 @@ -171,7 +171,7 @@ sub dnsbl_hit {
55    if (substr($rule, 0, 2) eq "__") {
56      # don't bother with meta rules
57    } elsif ($answer->type eq 'TXT') {
58 -    # txtdata returns a non- zone-file-format encoded result, unlike rdatastr;
59 +    # txtdata returns a non- zone-file-format encoded result, unlike rdstring;
60      # avoid space-separated RDATA <character-string> fields if possible,
61      # txtdata provides a list of strings in a list context since Net::DNS 0.69
62      $log = join('',$answer->txtdata);
63 @@ -215,12 +215,14 @@ sub dnsbl_uri {
64  
65    my $qname = $question->qname;
66  
67 -  # txtdata returns a non- zone-file-format encoded result, unlike rdatastr;
68 +  # txtdata returns a non- zone-file-format encoded result, unlike rdstring;
69    # avoid space-separated RDATA <character-string> fields if possible,
70    # txtdata provides a list of strings in a list context since Net::DNS 0.69
71    #
72 -  my $rdatastr = $answer->UNIVERSAL::can('txtdata') ? join('',$answer->txtdata)
73 -                                                    : $answer->rdatastr;
74 +  # rdatastr() is historical/undocumented, use rdstring() since Net::DNS 0.69
75 +  my $rdatastr = $answer->UNIVERSAL::can('txtdata')  ? join('',$answer->txtdata)
76 +               : $answer->UNIVERSAL::can('rdstring') ? $answer->rdstring
77 +                                                     : $answer->rdatastr;
78    if (defined $qname && defined $rdatastr) {
79      my $qclass = $question->qclass;
80      my $qtype = $question->qtype;
81 @@ -267,8 +269,13 @@ sub process_dnsbl_result {
82      my $answ_type = $answer->type;
83      # TODO: there are some CNAME returns that might be useful
84      next if ($answ_type ne 'A' && $answ_type ne 'TXT');
85 -    # skip any A record that isn't on 127/8
86 -    next if ($answ_type eq 'A' && $answer->rdatastr !~ /^127\./);
87 +    if ($answ_type eq 'A') {
88 +      # Net::DNS::RR::A::address() is available since Net::DNS 0.69
89 +      my $ip_address = $answer->UNIVERSAL::can('address') ? $answer->address
90 +                                                          : $answer->rdatastr;
91 +      # skip any A record that isn't on 127.0.0.0/8
92 +      next if $ip_address !~ /^127\./;
93 +    }
94      for my $rule (@{$rules}) {
95        $self->dnsbl_hit($rule, $question, $answer);
96      }
97 @@ -284,12 +291,14 @@ sub process_dnsbl_result {
98  sub process_dnsbl_set {
99    my ($self, $set, $question, $answer) = @_;
100  
101 -  # txtdata returns a non- zone-file-format encoded result, unlike rdatastr;
102 +  # txtdata returns a non- zone-file-format encoded result, unlike rdstring;
103    # avoid space-separated RDATA <character-string> fields if possible,
104    # txtdata provides a list of strings in a list context since Net::DNS 0.69
105    #
106 -  my $rdatastr = $answer->UNIVERSAL::can('txtdata') ? join('',$answer->txtdata)
107 -                                                    : $answer->rdatastr;
108 +  # rdatastr() is historical/undocumented, use rdstring() since Net::DNS 0.69
109 +  my $rdatastr = $answer->UNIVERSAL::can('txtdata')  ? join('',$answer->txtdata)
110 +               : $answer->UNIVERSAL::can('rdstring') ? $answer->rdstring
111 +                                                     : $answer->rdatastr;
112  
113    while (my ($subtest, $rule) = each %{ $self->{dnspost}->{$set} }) {
114      next if $self->{tests_already_hit}->{$rule};
115 diff --git a/lib/Mail/SpamAssassin/Plugin/AskDNS.pm b/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
116 index 3511162e8..4f41ff0ff 100644
117 --- a/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
118 +++ b/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
119 @@ -539,7 +539,7 @@ sub process_response_packet {
120      @answer = ( undef );
121    }
122  
123 -  # NOTE:  $rr->rdatastr returns the result encoded in a DNS zone file
124 +  # NOTE:  $rr->rdstring returns the result encoded in a DNS zone file
125    # format, i.e. enclosed in double quotes if a result contains whitespace
126    # (or other funny characters), and may use \DDD encoding or \X quoting as
127    # per RFC 1035.  Using $rr->txtdata instead avoids this unnecessary encoding
128 @@ -566,19 +566,24 @@ sub process_response_packet {
129        # special case, no answer records, only rcode can be tested
130      } else {
131        $rr_type = uc $rr->type;
132 -      if ($rr->UNIVERSAL::can('txtdata')) {  # TXT, SPF
133 -        # join with no intervening spaces, as per RFC 5518
134 +      if ($rr_type eq 'A') {
135 +        # Net::DNS::RR::A::address() is available since Net::DNS 0.69
136 +        $rr_rdatastr = $rr->UNIVERSAL::can('address') ? $rr->address
137 +                                                      : $rr->rdatastr;
138 +        if ($rr_rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/) {
139 +          $rdatanum = Mail::SpamAssassin::Util::my_inet_aton($rr_rdatastr);
140 +        }
141 +      } elsif ($rr->UNIVERSAL::can('txtdata')) {
142 +        # TXT, SPF: join with no intervening spaces, as per RFC 5518
143          if ($txtdata_can_provide_a_list || $rr_type ne 'TXT') {
144            $rr_rdatastr = join('', $rr->txtdata);  # txtdata() in list context!
145          } else {  # char_str_list() is only available for TXT records
146            $rr_rdatastr = join('', $rr->char_str_list);  # historical
147          }
148        } else {
149 -        $rr_rdatastr = $rr->rdatastr;
150 -        if ($rr_type eq 'A' &&
151 -            $rr_rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/) {
152 -          $rdatanum = Mail::SpamAssassin::Util::my_inet_aton($rr_rdatastr);
153 -        }
154 +        # rdatastr() is historical, use rdstring() since Net::DNS 0.69
155 +        $rr_rdatastr = $rr->UNIVERSAL::can('rdstring') ? $rr->rdstring
156 +                                                       : $rr->rdatastr;
157        }
158      # dbg("askdns: received rr type %s, data: %s", $rr_type, $rr_rdatastr);
159      }
160 diff --git a/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm b/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
161 index 9602eba4e..674f42bd4 100644
162 --- a/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
163 +++ b/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
164 @@ -942,9 +942,8 @@ sub complete_ns_lookup {
165      next unless (defined($str) && defined($dom));
166      dbg("uridnsbl: got($j) NS for $dom: $str");
167  
168 -    if ($str =~ /IN\s+NS\s+(\S+)/) {
169 -      my $nsmatch = lc $1;
170 -      $nsmatch =~ s/\.$//;
171 +    if ($rr->type eq 'NS') {
172 +      my $nsmatch = lc $rr->nsdname;  # available since at least Net::DNS 0.14
173        my $nsrhblstr = $nsmatch;
174        my $fullnsrhblstr = $nsmatch;
175  
176 @@ -1025,9 +1024,11 @@ sub complete_a_lookup {
177      }
178      dbg("uridnsbl: complete_a_lookup got(%d) A for %s: %s", $j,$hname,$str);
179  
180 -    local $1;
181 -    if ($str =~ /IN\s+A\s+(\S+)/) {
182 -      $self->lookup_dnsbl_for_ip($pms, $ent->{obj}, $1);
183 +    if ($rr->type eq 'A') {
184 +      # Net::DNS::RR::A::address() is available since Net::DNS 0.69
185 +      my $ip_address = $rr->UNIVERSAL::can('address') ? $rr->address
186 +                                                      : $rr->rdatastr;
187 +      $self->lookup_dnsbl_for_ip($pms, $ent->{obj}, $ip_address);
188      }
189    }
190  }
191 @@ -1038,7 +1039,8 @@ sub lookup_dnsbl_for_ip {
192    my ($self, $pms, $obj, $ip) = @_;
193  
194    local($1,$2,$3,$4);
195 -  $ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
196 +  $ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
197 +    or warn "lookup_dnsbl_for_ip: not an IPv4 address: $ip\n";
198    my $revip = "$4.$3.$2.$1";
199  
200    my $conf = $pms->{conf};
201 @@ -1100,12 +1102,14 @@ sub complete_dnsbl_lookup {
202      my $rr_type = $rr->type;
203  
204      if ($rr_type eq 'A') {
205 -      $rdatastr = $rr->rdatastr;
206 +      # Net::DNS::RR::A::address() is available since Net::DNS 0.69
207 +      $rdatastr = $rr->UNIVERSAL::can('address') ? $rr->address
208 +                                                 : $rr->rdatastr;
209        if ($rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
210          $rdatanum = Mail::SpamAssassin::Util::my_inet_aton($rdatastr);
211        }
212      } elsif ($rr_type eq 'TXT') {
213 -      # txtdata returns a non- zone-file-format encoded result, unlike rdatastr;
214 +      # txtdata returns a non- zone-file-format encoded result, unlike rdstring;
215        # avoid space-separated RDATA <character-string> fields if possible;
216        # txtdata provides a list of strings in list context since Net::DNS 0.69
217        $rdatastr = join('',$rr->txtdata);
218 commit 0d95b5b9280c3f8febffd418227a10f03747939a
219 Author: Mark Martinec <mmartinec@apache.org>
220 Date:   Mon Aug 10 23:43:53 2015 +0000
221
222     Bug 7236: Net::DNS assumes strings in TXT resource records are in UTF-8 and gratuitously tries to decodes it
223     
224     git-svn-id: https://svn.apache.org/repos/asf/spamassassin/trunk@1695182 13f79535-47bb-0310-9956-ffa450edef68
225
226 diff --git a/lib/Mail/SpamAssassin/Plugin/ASN.pm b/lib/Mail/SpamAssassin/Plugin/ASN.pm
227 index 3b406b00f..a5143fd12 100644
228 --- a/lib/Mail/SpamAssassin/Plugin/ASN.pm
229 +++ b/lib/Mail/SpamAssassin/Plugin/ASN.pm
230 @@ -355,8 +355,10 @@ sub process_dns_result {
231    foreach my $rr (@answer) {
232      dbg("asn: %s: lookup result packet: %s", $zone, $rr->string);
233      next if $rr->type ne 'TXT';
234 -    my @strings = $rr->char_str_list;
235 +    my @strings = Net::DNS->VERSION >= 0.69 ? $rr->txtdata
236 +                                            : $rr->char_str_list;
237      next if !@strings;
238 +    for (@strings) { utf8::encode($_) if utf8::is_utf8($_) }
239  
240      my @items;
241      if (@strings > 1 && join('',@strings) !~ m{\|}) {
242 diff --git a/lib/Mail/SpamAssassin/Plugin/AskDNS.pm b/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
243 index 4cb37e1e1..b9b880ec3 100644
244 --- a/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
245 +++ b/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
246 @@ -574,6 +574,7 @@ sub process_response_packet {
247          if ($rr_rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/) {
248            $rdatanum = Mail::SpamAssassin::Util::my_inet_aton($rr_rdatastr);
249          }
250 +
251        } elsif ($rr->UNIVERSAL::can('txtdata')) {
252          # TXT, SPF: join with no intervening spaces, as per RFC 5518
253          if ($txtdata_can_provide_a_list || $rr_type ne 'TXT') {
254 @@ -581,10 +582,22 @@ sub process_response_packet {
255          } else {  # char_str_list() is only available for TXT records
256            $rr_rdatastr = join('', $rr->char_str_list);  # historical
257          }
258 +        # Net::DNS attempts to decode text strings in a TXT record as UTF-8,
259 +        # which is bad: octets failing the UTF-8 decoding are converted to
260 +        # three octets \x{EF}\x{BF}\x{BD} (i.e. to a Unicode "replacement
261 +        # character" U+FFFD encoded as UTF-8), and ASCII text is unnecessarily
262 +        # flagged as perl native characters (utf8 flag on), which can be
263 +        # disruptive on later processing, e.g. implicitly upgrading strings
264 +        # on concatenation. Unfortunately there is no way of legally bypassing
265 +        # the UTF-8 decoding by Net::DNS::RR::TXT in Net::DNS::RR::Text.
266 +        # Try to minimize damage by encoding back to UTF-8 octets:
267 +        utf8::encode($rr_rdatastr)  if utf8::is_utf8($rr_rdatastr);
268 +
269        } else {
270          # rdatastr() is historical, use rdstring() since Net::DNS 0.69
271          $rr_rdatastr = $rr->UNIVERSAL::can('rdstring') ? $rr->rdstring
272                                                         : $rr->rdatastr;
273 +        utf8::encode($rr_rdatastr)  if utf8::is_utf8($rr_rdatastr);
274        }
275      # dbg("askdns: received rr type %s, data: %s", $rr_type, $rr_rdatastr);
276      }
277 diff --git a/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm b/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
278 index a35037dbe..978a2af7d 100644
279 --- a/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
280 +++ b/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
281 @@ -1108,6 +1108,7 @@ sub complete_dnsbl_lookup {
282        # avoid space-separated RDATA <character-string> fields if possible;
283        # txtdata provides a list of strings in list context since Net::DNS 0.69
284        $rdatastr = join('',$rr->txtdata);
285 +      utf8::encode($rdatastr)  if utf8::is_utf8($rdatastr);
286      } else {
287        next;
288      }
289 commit 7e9872e5b45a7793b9aabeb513e45acde9dce652
290 Author: Mark Martinec <mmartinec@apache.org>
291 Date:   Tue Aug 11 16:22:27 2015 +0000
292
293     Bug 7236: Net::DNS assumes strings in TXT resource records are in UTF-8 and gratuitously tries to decodes it (also in Dns.pm)
294     
295     git-svn-id: https://svn.apache.org/repos/asf/spamassassin/trunk@1695336 13f79535-47bb-0310-9956-ffa450edef68
296
297 diff --git a/lib/Mail/SpamAssassin/Dns.pm b/lib/Mail/SpamAssassin/Dns.pm
298 index 0926a030e..014ab2bf6 100644
299 --- a/lib/Mail/SpamAssassin/Dns.pm
300 +++ b/lib/Mail/SpamAssassin/Dns.pm
301 @@ -177,6 +177,7 @@ sub dnsbl_hit {
302      # avoid space-separated RDATA <character-string> fields if possible,
303      # txtdata provides a list of strings in a list context since Net::DNS 0.69
304      $log = join('',$answer->txtdata);
305 +    utf8::encode($log)  if utf8::is_utf8($log);
306      local $1;
307      $log =~ s{ (?<! [<(\[] ) (https? : // \S+)}{<$1>}xgi;
308    } else {  # assuming $answer->type eq 'A'
309 @@ -215,16 +216,27 @@ sub dnsbl_hit {
310  sub dnsbl_uri {
311    my ($self, $question, $answer) = @_;
312  
313 -  my $qname = $question->qname;
314 +  my $rdatastr;
315 +  if ($answer->UNIVERSAL::can('txtdata')) {
316 +    # txtdata returns a non- zone-file-format encoded result, unlike rdstring;
317 +    # avoid space-separated RDATA <character-string> fields if possible,
318 +    # txtdata provides a list of strings in a list context since Net::DNS 0.69
319 +    $rdatastr = join('',$answer->txtdata);
320 +  } else {
321 +    # rdatastr() is historical/undocumented, use rdstring() since Net::DNS 0.69
322 +    $rdatastr = $answer->UNIVERSAL::can('rdstring') ? $answer->rdstring
323 +                                                    : $answer->rdatastr;
324 +    # encoded in a RFC 1035 zone file format (escaped), decode it
325 +    $rdatastr =~ s{ \\ ( [0-9]{3} | (?![0-9]{3}) . ) }
326 +                  { length($1)==3 && $1 <= 255 ? chr($1) : $1 }xgse;
327 +  }
328 +  # Bug 7236: Net::DNS attempts to decode text strings in a TXT record as
329 +  # UTF-8 since version 0.69, which is undesired: octets failing the UTF-8
330 +  # decoding are converted to a Unicode "replacement character" U+FFFD, and
331 +  # ASCII text is unnecessarily flagged as perl native characters.
332 +  utf8::encode($rdatastr)  if utf8::is_utf8($rdatastr);
333  
334 -  # txtdata returns a non- zone-file-format encoded result, unlike rdstring;
335 -  # avoid space-separated RDATA <character-string> fields if possible,
336 -  # txtdata provides a list of strings in a list context since Net::DNS 0.69
337 -  #
338 -  # rdatastr() is historical/undocumented, use rdstring() since Net::DNS 0.69
339 -  my $rdatastr = $answer->UNIVERSAL::can('txtdata')  ? join('',$answer->txtdata)
340 -               : $answer->UNIVERSAL::can('rdstring') ? $answer->rdstring
341 -                                                     : $answer->rdatastr;
342 +  my $qname = $question->qname;
343    if (defined $qname && defined $rdatastr) {
344      my $qclass = $question->qclass;
345      my $qtype = $question->qtype;
346 @@ -293,14 +305,25 @@ sub process_dnsbl_result {
347  sub process_dnsbl_set {
348    my ($self, $set, $question, $answer) = @_;
349  
350 -  # txtdata returns a non- zone-file-format encoded result, unlike rdstring;
351 -  # avoid space-separated RDATA <character-string> fields if possible,
352 -  # txtdata provides a list of strings in a list context since Net::DNS 0.69
353 -  #
354 -  # rdatastr() is historical/undocumented, use rdstring() since Net::DNS 0.69
355 -  my $rdatastr = $answer->UNIVERSAL::can('txtdata')  ? join('',$answer->txtdata)
356 -               : $answer->UNIVERSAL::can('rdstring') ? $answer->rdstring
357 -                                                     : $answer->rdatastr;
358 +  my $rdatastr;
359 +  if ($answer->UNIVERSAL::can('txtdata')) {
360 +    # txtdata returns a non- zone-file-format encoded result, unlike rdstring;
361 +    # avoid space-separated RDATA <character-string> fields if possible,
362 +    # txtdata provides a list of strings in a list context since Net::DNS 0.69
363 +    $rdatastr = join('',$answer->txtdata);
364 +  } else {
365 +    # rdatastr() is historical/undocumented, use rdstring() since Net::DNS 0.69
366 +    $rdatastr = $answer->UNIVERSAL::can('rdstring') ? $answer->rdstring
367 +                                                    : $answer->rdatastr;
368 +    # encoded in a RFC 1035 zone file format (escaped), decode it
369 +    $rdatastr =~ s{ \\ ( [0-9]{3} | (?![0-9]{3}) . ) }
370 +                  { length($1)==3 && $1 <= 255 ? chr($1) : $1 }xgse;
371 +  }
372 +  # Bug 7236: Net::DNS attempts to decode text strings in a TXT record as
373 +  # UTF-8 since version 0.69, which is undesired: octets failing the UTF-8
374 +  # decoding are converted to a Unicode "replacement character" U+FFFD, and
375 +  # ASCII text is unnecessarily flagged as perl native characters.
376 +  utf8::encode($rdatastr)  if utf8::is_utf8($rdatastr);
377  
378    while (my ($subtest, $rule) = each %{ $self->{dnspost}->{$set} }) {
379      next if $self->{tests_already_hit}->{$rule};
380 diff --git a/lib/Mail/SpamAssassin/Plugin/AskDNS.pm b/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
381 index d26117bbb..ac533a779 100644
382 --- a/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
383 +++ b/lib/Mail/SpamAssassin/Plugin/AskDNS.pm
384 @@ -584,9 +584,9 @@ sub process_response_packet {
385            $rr_rdatastr = join('', $rr->char_str_list);  # historical
386          }
387          # Net::DNS attempts to decode text strings in a TXT record as UTF-8,
388 -        # which is bad: octets failing the UTF-8 decoding are converted to
389 -        # three octets \x{EF}\x{BF}\x{BD} (i.e. to a Unicode "replacement
390 -        # character" U+FFFD encoded as UTF-8), and ASCII text is unnecessarily
391 +        # which is undesired: octets failing the UTF-8 decoding are converted
392 +        # to a Unicode "replacement character" U+FFFD (encoded as octets
393 +        # \x{EF}\x{BF}\x{BD} in UTF-8), and ASCII text is unnecessarily
394          # flagged as perl native characters (utf8 flag on), which can be
395          # disruptive on later processing, e.g. implicitly upgrading strings
396          # on concatenation. Unfortunately there is no way of legally bypassing