Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package perl-Text-CSV_XS for openSUSE:Factory checked in at 2026-10-21 20:51:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/perl-Text-CSV_XS (Old) and /work/SRC/openSUSE:Factory/.perl-Text-CSV_XS.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "perl-Text-CSV_XS" Wed Oct 21 20:51:47 2026 rev:62 rq:1386512 version:1.650.0 Changes: -------- --- /work/SRC/openSUSE:Factory/perl-Text-CSV_XS/perl-Text-CSV_XS.changes 2026-06-30 15:11:51.536843644 +0200 +++ /work/SRC/openSUSE:Factory/.perl-Text-CSV_XS.new.2252680/perl-Text-CSV_XS.changes 2026-10-21 20:51:51.434803639 +0200 @@ -1,0 +2,17 @@ +Thu Oct 15 09:58:32 UTC 2026 - Tina Müller <[email protected]> + +- updated to 1.650.0 (1.65) + see /usr/share/doc/packages/perl-Text-CSV_XS/ChangeLog + + 1.65 - 2026-10-15, H.Merijn Brand + * CPAN.org does not forward email anymore + * parse (): report trailing lone CR, do not store EOF as 0xFF (CVE-2026-110759) bsc#1286128 + * Under allow_whitespace, do not strip beyond field start (CVE-2026-110758) bsc#1286137 + * combine (): decide quoting from the bytes, not the NV flags (CVE-2026-110757) bsc#1286191 + * Flush saved bytes in Combine () (CVE-2026-110756) bsc#1286130 + * Re-use AHEAD with \r instead of copying (CVE-2026-110755) bsc#1286198 + * getline (): free the result array when the parse fails (CVE-2026-110754) bsc#1286188 + * Do not free the bound column scalar in skip_empty_rows (CVE-2026-110753) bsc#1286187 + * fragment (): do not size memory by the selector integers (CVE-2026-110752) bsc#1286138 + +------------------------------------------------------------------- @@ -376 +393,2 @@ - * Small additional fix for eol = + BOM + * Small additional fix for eol = + + BOM Old: ---- Text-CSV_XS-1.64.tgz New: ---- Text-CSV_XS-1.65.tgz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ perl-Text-CSV_XS.spec ++++++ --- /var/tmp/diff_new_pack.1vDgs9/_old 2026-10-21 20:51:52.429845134 +0200 +++ /var/tmp/diff_new_pack.1vDgs9/_new 2026-10-21 20:51:52.432845259 +0200 @@ -18,10 +18,10 @@ %define cpan_name Text-CSV_XS Name: perl-Text-CSV_XS -Version: 1.640.0 +Version: 1.650.0 Release: 0 -# 1.64 -> normalize -> 1.640.0 -%define cpan_version 1.64 +# 1.65 -> normalize -> 1.650.0 +%define cpan_version 1.65 License: Artistic-1.0 OR GPL-1.0-or-later Summary: Comma-Separated Values manipulation routines URL: https://metacpan.org/release/%{cpan_name} ++++++ Text-CSV_XS-1.64.tgz -> Text-CSV_XS-1.65.tgz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/CSV_XS.pm new/Text-CSV_XS-1.65/CSV_XS.pm --- old/Text-CSV_XS-1.64/CSV_XS.pm 2026-06-22 12:57:04.000000000 +0200 +++ new/Text-CSV_XS-1.65/CSV_XS.pm 2026-10-14 17:37:14.000000000 +0200 @@ -23,7 +23,7 @@ use Carp; use vars qw( $VERSION @ISA @EXPORT_OK %EXPORT_TAGS ); -$VERSION = "1.64"; +$VERSION = "1.65"; @ISA = qw( Exporter ); XSLoader::load ("Text::CSV_XS", $VERSION); @@ -1154,7 +1154,10 @@ my ($tlr, $tlc, $brr, $brc) = @{$s}; $r < $tlr || ($brr ne "*" && $r > $brr) and next; !defined $lc || $tlc < $lc and $lc = $tlc; - my $rr = $brc eq "*" ? $#{$row} : $brc; + # Do not pad past the data: an explicit column beyond the + # row would otherwise allocate one slot per selector integer + my $lr = $#{$row} > $tlc ? $#{$row} : $tlc; + my $rr = $brc eq "*" ? $#{$row} : $brc > $lr ? $lr : $brc; $row{$_} = $row->[$_] for $tlc .. $rr; } push @c, [ @row{sort { $a <=> $b } keys %row } ]; @@ -1168,7 +1171,11 @@ } # row or col + # Keep the ranges as pairs and test membership per row or column: + # materialising every index in an array cost memory proportional + # to the integers in the selector, not to the data my @r; + my $max = 0; my $eod = 0; for (split m/\s*;\s*/ => $range) { my ($from, $to) = m/^\s* ([0-9]+) (?: \s* - \s* ([0-9]+ | \* ))? \s* $/x @@ -1177,16 +1184,21 @@ $to eq "*" and ($to, $eod) = ($from, 1); # $to cannot be <= 0 due to regex and ||= $from <= 0 || $to < $from and croak ($self->SetDiag (2013)); - $r[$_] = 1 for $from .. $to; + push @r => [ $from, $to ]; + $to > $max and $max = $to; } + my $in = sub { + my $i = shift; + $i > $max and return $eod; + $i >= $_->[0] && $i <= $_->[1] and return 1 for @r; + return 0; + }; my $r = 0; - $type eq "col" and shift @r; - $_ ||= 0 for @r; while (my $row = $self->getline ($io)) { $r++; if ($type eq "row") { - if (($r > $#r && $eod) || $r[$r]) { + if ($in->($r)) { push @c, $row; if (@h) { my %h; @h{@h} = @{$c[-1]}; @@ -1195,7 +1207,7 @@ } next; } - push @c, [ map { ($_ > $#r && $eod) || $r[$_] ? $row->[$_] : () } 0..$#{$row} ]; + push @c, [ map { $in->($_ + 1) ? $row->[$_] : () } 0..$#{$row} ]; if (@h) { my %h; @h{@h} = @{$c[-1]}; $c[-1] = \%h; @@ -2907,6 +2919,10 @@ $csv->column_names ("Name", "Age"); my $AoH = $csv->fragment ($fh, "col=3;8"); +When passing indices beyond the content of the rows, only the cells parsed +will be passed: C<col=1-9> will not return 9 entries if a row only has four +fields. + If the L</after_parse> callback is active, it is also called on every line parsed and skipped before the fragment. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/CSV_XS.xs new/Text-CSV_XS-1.65/CSV_XS.xs --- old/Text-CSV_XS-1.64/CSV_XS.xs 2026-05-03 21:08:08.000000000 +0200 +++ new/Text-CSV_XS-1.65/CSV_XS.xs 2026-10-14 16:35:20.000000000 +0200 @@ -1112,9 +1112,9 @@ csv->fld_idx = 0; \ c = CSV_GET; \ if (c == EOF || ser == 2) { \ - sv_free (sv); \ + unless (csv->is_bound) sv_free (sv); \ sv = NULL; \ - seenSomething = FALSE; \ + seenSomething = FALSE; \ if (ser == 2) return FALSE; \ break; \ } \ @@ -1137,7 +1137,7 @@ SPAGAIN; \ unless (result) { \ /* A false return will stop the parsing */ \ - sv_free (sv); \ + unless (csv->is_bound) sv_free (sv); \ sv = NULL; \ waitingForField = 0; \ return FALSE; \ @@ -1245,8 +1245,7 @@ * (always_quote), if binary or blank characters are found * and if the string contains quote or escape characters. */ - if (!quoteMe && - ( quoteMe = (!SvIOK (sv) && !SvNOK (sv) && CH_QUOTE))) { + if (!quoteMe && (quoteMe = CH_QUOTE)) { char *ptr2; STRLEN l; @@ -1353,8 +1352,20 @@ while (len--) CSV_PUT (csv, dst, *ptr++); } - if (csv->used) - return Print (csv, dst); + if (csv->used) { + unless (Print (csv, dst)) + return FALSE; + if (csv->used) { + /* Print () held back trailing bytes that are not valid UTF-8 + * to complete them on the next flush. This was the last flush + * of the record, so write them now, as they are */ + int r, utf8 = csv->utf8; + csv->utf8 = 0; + r = Print (csv, dst); + csv->utf8 = utf8; + return r; + } + } return TRUE; } /* Combine */ @@ -1586,7 +1597,7 @@ STRLEN len; char *s = SvPV (sv, len); unless (s && len) return; - while (s[len - 1] == CH_SPACE || s[len - 1] == CH_TAB) + while (len && (s[len - 1] == CH_SPACE || s[len - 1] == CH_TAB)) s[--len] = (char)0; SvCUR_set (sv, len); } /* strip_trail_whitespace */ @@ -2102,6 +2113,11 @@ #endif unless (seenSomething) break; + /* parse (): a CR at the very end of the input is not part + * of an EOL, as for a non-empty last field; do not go on + * with EOF as if it were a data byte */ + unless (csv->useIO) + ERROR_INSIDE_FIELD (2032); goto restart; } @@ -2474,8 +2490,17 @@ if (csv.useIO) { if (csv.tmp && csv.used < csv.size && csv.has_ahead) { - SV *sv = newSVpvn (csv.bptr + csv.used, csv.size - csv.used); - (void)hv_store (hv, "_AHEAD", 6, sv, 0); + SV **svp; + if (ahead && SvREFCNT (csv.tmp) == 1 + && (svp = hv_fetchs (hv, "_AHEAD", FALSE)) && *svp == csv.tmp) + /* The remainder already lives in _AHEAD: drop the parsed + * part in place instead of copying the whole tail again, + * which made CR-only input quadratic */ + sv_chop (csv.tmp, csv.bptr + csv.used); + else { + SV *sv = newSVpvn (csv.bptr + csv.used, csv.size - csv.used); + (void)hv_store (hv, "_AHEAD", 6, sv, 0); + } } else { csv.has_ahead = 0; @@ -2604,11 +2629,15 @@ } av_push (avr, newRV_noinc ((SV *)row)); - if (n >= length && skip >= 0) + if (n >= length && skip >= 0) { + row = NULL; break; /* We have enough */ + } row = newAV (); } + if (row) + SvREFCNT_dec ((SV *)row); while (n > length) { SvREFCNT_dec (av_pop (avr)); n--; @@ -2734,7 +2763,7 @@ CSV_XS_SELF; if (fields == &PL_sv_undef) - av = newAV (); + av = (AV *)sv_2mortal ((SV *)newAV ()); else { unless (_is_arrayref (fields)) croak ("Expected fields to be an array ref"); @@ -2759,6 +2788,8 @@ av = newAV (); avf = newAV (); r = xsParse (self, hv, av, avf, io, 1); + unless (r) + SvREFCNT_dec ((SV *)av); PUT_RETURN (r ? sv_2mortal (newRV_noinc ((SV *)av)) : undef); /* XS getline */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/ChangeLog new/Text-CSV_XS-1.65/ChangeLog --- old/Text-CSV_XS-1.64/ChangeLog 2026-06-22 14:46:30.000000000 +0200 +++ new/Text-CSV_XS-1.65/ChangeLog 2026-10-15 09:00:43.000000000 +0200 @@ -1,3 +1,14 @@ +1.65 - 2026-10-15, H.Merijn Brand + * CPAN.org does not forward email anymore + * parse (): report trailing lone CR, do not store EOF as 0xFF (CVE-2026-110759) + * Under allow_whitespace, do not strip beyond field start (CVE-2026-110758) + * combine (): decide quoting from the bytes, not the NV flags (CVE-2026-110757) + * Flush saved bytes in Combine () (CVE-2026-110756) + * Re-use AHEAD with \r instead of copying (CVE-2026-110755) + * getline (): free the result array when the parse fails (CVE-2026-110754) + * Do not free the bound column scalar in skip_empty_rows (CVE-2026-110753) + * fragment (): do not size memory by the selector integers (CVE-2026-110752) + 1.64 - 2026-06-22, H.Merijn Brand * Fix syntax error in csv2xlsx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/MANIFEST new/Text-CSV_XS-1.65/MANIFEST --- old/Text-CSV_XS-1.64/MANIFEST 2026-06-22 14:48:23.000000000 +0200 +++ new/Text-CSV_XS-1.65/MANIFEST 2026-10-15 10:00:55.000000000 +0200 @@ -43,6 +43,7 @@ t/90_csv.t Function csv () checks t/91_csv_cb.t Callbacks with csv function t/92_stream.t Streaming functionality +t/97_parse_eof.t Trailing CR at the end of parse () input t/99_mem.t Overlong attribute guard check t/util.pl Extra test utilities examples/csv2xls Script to onvert CSV files to M$Excel XLS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/META.json new/Text-CSV_XS-1.65/META.json --- old/Text-CSV_XS-1.64/META.json 2026-06-22 14:48:24.000000000 +0200 +++ new/Text-CSV_XS-1.65/META.json 2026-10-15 10:00:56.000000000 +0200 @@ -1,8 +1,48 @@ { - "dynamic_config" : 1, - "abstract" : "Comma-Separated Values manipulation routines", - "name" : "Text-CSV_XS", + "license" : [ + "perl_5" + ], + "resources" : { + "license" : [ + "http://dev.perl.org/licenses/" + ], + "homepage" : "https://metacpan.org/pod/Text::CSV_XS", + "x_IRC" : "irc://irc.perl.org/#csv", + "bugtracker" : { + "web" : "https://github.com/Tux/Text-CSV_XS/issues" + }, + "repository" : { + "type" : "git", + "url" : "https://github.com/Tux/Text-CSV_XS", + "web" : "https://github.com/Tux/Text-CSV_XS" + } + }, + "release_status" : "stable", + "provides" : { + "Text::CSV_XS" : { + "version" : "1.65", + "file" : "CSV_XS.pm" + } + }, + "author" : [ + "H.Merijn Brand <[email protected]>" + ], + "generated_by" : "Author", + "meta-spec" : { + "version" : 2, + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec" + }, "prereqs" : { + "runtime" : { + "requires" : { + "XSLoader" : "0", + "perl" : "5.008001", + "IO::Handle" : "0" + }, + "recommends" : { + "Encode" : "3.24" + } + }, "configure" : { "recommends" : { "ExtUtils::MakeMaker" : "7.78" @@ -11,60 +51,20 @@ "ExtUtils::MakeMaker" : "0" } }, - "test" : { - "requires" : { - "Test::More" : "0", - "Tie::Scalar" : "0" - } - }, - "runtime" : { - "recommends" : { - "Encode" : "3.24" - }, + "build" : { "requires" : { - "XSLoader" : "0", - "IO::Handle" : "0", - "perl" : "5.008001" + "Config" : "0" } }, - "build" : { + "test" : { "requires" : { - "Config" : "0" + "Test::More" : "0", + "Tie::Scalar" : "0" } } }, - "release_status" : "stable", - "author" : [ - "H.Merijn Brand <[email protected]>" - ], - "meta-spec" : { - "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", - "version" : 2 - }, - "version" : "1.64", - "generated_by" : "Author", - "provides" : { - "Text::CSV_XS" : { - "file" : "CSV_XS.pm", - "version" : "1.64" - } - }, - "license" : [ - "perl_5" - ], - "resources" : { - "x_IRC" : "irc://irc.perl.org/#csv", - "repository" : { - "type" : "git", - "url" : "https://github.com/Tux/Text-CSV_XS", - "web" : "https://github.com/Tux/Text-CSV_XS" - }, - "license" : [ - "http://dev.perl.org/licenses/" - ], - "homepage" : "https://metacpan.org/pod/Text::CSV_XS", - "bugtracker" : { - "web" : "https://github.com/Tux/Text-CSV_XS/issues" - } - } + "name" : "Text-CSV_XS", + "abstract" : "Comma-Separated Values manipulation routines", + "dynamic_config" : 1, + "version" : "1.65" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/META.yml new/Text-CSV_XS-1.65/META.yml --- old/Text-CSV_XS-1.64/META.yml 2026-06-22 14:48:24.000000000 +0200 +++ new/Text-CSV_XS-1.65/META.yml 2026-10-15 10:00:56.000000000 +0200 @@ -1,13 +1,13 @@ --- abstract: Comma-Separated Values manipulation routines author: - - H.Merijn Brand <[email protected]> + - H.Merijn Brand <[email protected]> build_requires: Config: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 -generated_by: Author, CPAN::Meta::Converter version 2.150013 +generated_by: Author, CPAN::Meta::Converter version 2.150015 license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -16,7 +16,7 @@ provides: Text::CSV_XS: file: CSV_XS.pm - version: '1.64' + version: '1.65' recommends: Encode: '3.24' requires: @@ -31,4 +31,4 @@ homepage: https://metacpan.org/pod/Text::CSV_XS license: http://dev.perl.org/licenses/ repository: https://github.com/Tux/Text-CSV_XS -version: '1.64' +version: '1.65' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/SECURITY.md new/Text-CSV_XS-1.65/SECURITY.md --- old/Text-CSV_XS-1.64/SECURITY.md 2025-06-05 21:10:20.000000000 +0200 +++ new/Text-CSV_XS-1.65/SECURITY.md 2026-09-02 13:08:39.000000000 +0200 @@ -1,6 +1,6 @@ # Security Policy for the Text::CSV_XS distribution. -Report security issues by email to H.Merijn Brand <[email protected]>. +Report security issues by email to H.Merijn Brand <[email protected]>. This is the Security Policy for Text::CSV_XS. @@ -14,7 +14,7 @@ # How to Report a Security Vulnerability Security vulnerabilities can be reported to the current Text::CSV_XS -maintainers by email to H.Merijn Brand <[email protected]>. +maintainers by email to H.Merijn Brand <[email protected]>. Please include as many details as possible, including code samples or test cases, so that we can reproduce the issue. Check that your diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/ppport.h new/Text-CSV_XS-1.65/ppport.h --- old/Text-CSV_XS-1.64/ppport.h 2026-05-03 21:08:08.000000000 +0200 +++ new/Text-CSV_XS-1.65/ppport.h 2026-09-28 14:45:30.000000000 +0200 @@ -6,7 +6,7 @@ ppport.h -- Perl/Pollution/Portability Version 3.73 - Automatically created by Devel::PPPort running under perl 5.042002. + Automatically created by Devel::PPPort running under perl 5.044000. Do NOT edit this file directly! -- Edit PPPort_pm.PL and the includes in parts/inc/ instead. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/t/46_eol_si.t new/Text-CSV_XS-1.65/t/46_eol_si.t --- old/Text-CSV_XS-1.64/t/46_eol_si.t 2026-05-03 10:14:23.000000000 +0200 +++ new/Text-CSV_XS-1.65/t/46_eol_si.t 2026-10-14 16:09:53.000000000 +0200 @@ -13,7 +13,7 @@ plan skip_all => "No reliable perlIO available"; } else { - plan tests => 562; + plan tests => 568; } } @@ -257,4 +257,36 @@ is ($foo, "x\n", "say, with newline"); } +{ # CR-only input arrives from getline () as one chunk; each record must + # not copy the whole rest of that chunk again + my $data = join "" => map { "$_,x\r" } 1 .. 5; + open my $fh, "<", \$data or die "$!"; + my $csv = Text::CSV_XS->new ({ binary => 1 }); + my (@rows, %ahead); + while (my $row = $csv->getline ($fh)) { + push @rows, $row->[0]; + $ahead{0 + \$csv->{_AHEAD}}++ if defined $csv->{_AHEAD} and @rows > 1; + } + is_deeply (\@rows, [ 1 .. 5 ], "All CR-only records parsed"); + is (scalar keys %ahead, 1, "The unparsed remainder is kept, not re-copied"); + + # header () hands its remainder over the same way + $data = "a,b\r" . join "" => map { "$_,x\r" } 1 .. 5; + open $fh, "<", \$data or die "$!"; + $csv = Text::CSV_XS->new ({ binary => 1 }); + is_deeply ([ $csv->header ($fh) ], [qw( a b )], "Header from CR-only input"); + @rows = (); + while (my $row = $csv->getline ($fh)) { push @rows, $row->[0] } + is_deeply (\@rows, [ 1 .. 5 ], "All records after the header"); + + # Same data with LF still behaves + $data = join "" => map { "$_,x\n" } 1 .. 5; + open $fh, "<", \$data or die "$!"; + $csv = Text::CSV_XS->new ({ binary => 1 }); + @rows = (); + while (my $row = $csv->getline ($fh)) { push @rows, $row->[0] } + is_deeply (\@rows, [ 1 .. 5 ], "LF records"); + ok ($csv->eof, "At EOF"); + } + 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/t/50_utf8.t new/Text-CSV_XS-1.65/t/50_utf8.t --- old/Text-CSV_XS-1.64/t/50_utf8.t 2026-05-03 09:59:50.000000000 +0200 +++ new/Text-CSV_XS-1.65/t/50_utf8.t 2026-10-14 15:58:59.000000000 +0200 @@ -3,6 +3,7 @@ use strict; use warnings; +use Config; use Test::More; use charnames ":full"; @@ -11,7 +12,7 @@ plan skip_all => "UTF8 tests useless in this ancient perl version"; } else { - plan tests => 93; + plan tests => 99; } } @@ -115,8 +116,7 @@ close $fh; open $fh, "<:encoding(utf-8)", $tfn or die "$tfn: $!\n"; -SKIP: { - my $out = ""; +{ my $out = ""; my $isutf8 = sub { utf8::is_utf8 ($out); }; ok ($csv->auto_diag (1), "auto diag"); ok ($csv->binary (1), "set binary"); @@ -135,3 +135,48 @@ ok (!$isutf8->(1), "not utf8"); close $fh; } + +if (exists $Config{useperlio} && + defined $Config{useperlio} && + $Config{useperlio} eq "define") { + # A decoded field followed by bytes that are not valid UTF-8 must not + # lose the end of the record when written with print () + my $u = "\x{e9}"; + utf8::upgrade ($u); + + sub emit { + my $csv = Text::CSV_XS->new ({ binary => 1, eol => "\n" }); + my $out = ""; + open my $fh, ">", \$out or die "$!"; + $csv->print ($fh, $_) for @_; + close $fh; + $out; + } # emit + + sub rows { + my $s = shift; + open my $fh, "<", \$s or die "$!"; + my $csv = Text::CSV_XS->new ({ binary => 1 }); + my @r; + while (my $r = $csv->getline ($fh)) { push @r, $r } + scalar @r; + } # rows + + my $out = emit ([ $u, "\xe9" ], [ "next", "row" ]); + like ($out, qr{\xe9\n}, "Trailing byte and EOL written"); + is (rows ($out), 2, "Two records, not fused"); + + $out = emit ([ $u, qq{x\xe9"} ], [ "next", "row" ]);#" + like ($out, qr{"\n}, "Closing quote and EOL written"); + is (rows ($out), 2, "Quote closed, next record intact"); + + my $v = "\x{e8}"; + utf8::upgrade ($v); + is (rows (emit ([ $u, $v ], [ "next", "row" ])), 2, "Both decoded"); + is (rows (emit ([ "\xc3\xa9", "\xe9" ], [ "next", "row" ])), 2, "Both bytes"); + } +else { + ok (1, "No PerlIO avail for this test") for 1..6; + } + +1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/t/55_combi.t new/Text-CSV_XS-1.65/t/55_combi.t --- old/Text-CSV_XS-1.64/t/55_combi.t 2022-09-13 09:37:35.000000000 +0200 +++ new/Text-CSV_XS-1.65/t/55_combi.t 2026-10-14 15:40:51.000000000 +0200 @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 25119; +use Test::More tests => 25127; BEGIN { require_ok "Text::CSV_XS"; @@ -141,7 +141,7 @@ { ok (my $csv = Text::CSV_XS->new ({ binary => 1 }), "New CSV default"); ok ($csv->combine ("=\x00="), "combine =\\x00="); - is ($csv->string, qq{"="0="}, "string"); + is ($csv->string, qq{"="0="}, "string");#" } { ok (my $csv = Text::CSV_XS->new ({ binary => 1, escape_null => 0 }), "New CSV no escape_null"); ok ($csv->combine ("=\x00="), "combine =\\x00="); @@ -155,4 +155,28 @@ ok ($csv->combine ("=\x00="), "combine =\\x00="); is ($csv->string, qq{"=\0="}, "string"); } + +{ # A string that was used as a number keeps its bytes: quote on content + my $csv = Text::CSV_XS->new ({ binary => 1 }); + + my $v = "12\n"; + my $p = $v > 0; + ok ($csv->combine ("a", $v, "b"), "combine numeric-looking string with NL"); + is ($csv->string, qq{a,"12\n",b}, "NL field quoted after numeric use"); + + my $n = "1.5\r\n"; + my $x = $n + 0; + ok ($csv->combine ("a", $n, "b"), "combine NV-flagged string with CRLF"); + is ($csv->string, qq{a,"1.5\r\n",b}, "CRLF field quoted after numeric use"); + + my $s = " 12"; + my $e = $s == 12; + ok ($csv->combine ("a", $s, "b"), "combine numeric-looking string with space"); + is ($csv->string, qq{a," 12",b}, "quote_space honoured after numeric use"); + + # Real numbers are still written bare + ok ($csv->combine (1, 2.5, -3), "combine numbers"); + is ($csv->string, "1,2.5,-3", "Numbers unquoted"); + } + 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/t/60_samples.t new/Text-CSV_XS-1.65/t/60_samples.t --- old/Text-CSV_XS-1.64/t/60_samples.t 2016-08-12 08:18:59.000000000 +0200 +++ new/Text-CSV_XS-1.65/t/60_samples.t 2026-10-15 09:41:55.000000000 +0200 @@ -3,18 +3,18 @@ use strict; use warnings; -use Test::More tests => 8; +use Config; +use Test::More tests => 12; BEGIN { use_ok "Text::CSV_XS", (); plan skip_all => "Cannot load Text::CSV_XS" if $@; } -# Some assorted examples from the modules history +# Some assorted examples from the modules history and other sources # "Pavel Kotala" <[email protected]> -{ - my $csv = Text::CSV_XS->new ({ +{ my $csv = Text::CSV_XS->new ({ quote_char => '"', escape_char => '\\', sep_char => ';', @@ -32,3 +32,54 @@ is ($list[0], $olist[0], "field 1"); is ($list[1], $olist[1], "field 2"); } + +if (-e "/proc/self/statm" and + exists $Config{useperlio} && + defined $Config{useperlio} && + $Config{useperlio} eq "define") { + sub rss { + open my $fh, "<", "/proc/self/statm" or die "$!"; + (split m/\s+/ => scalar <$fh>)[1] * 4096; + } # rss + + my $csv = Text::CSV_XS->new ({ binary => 1 }); + my $width = 20_000; + my $good = ("," x $width) . "\n"; + my $bad = ("," x $width) . qq{"a"b\n}; + + # Warm up allocator and cache + for (1 .. 3) { + open my $fh, "<", \$good or die "$!"; + $csv->getline ($fh); + open $fh, "<", \$bad or die "$!"; + $csv->getline ($fh); + } + + my $r0 = rss (); + for (1 .. 40) { + open my $fh, "<", \$bad or die "$!"; + $csv->getline ($fh); + } + my $grow = rss () - $r0; + is (($csv->error_diag)[0], 2023, "Records fail to parse"); + cmp_ok ($grow, "<", 8_000_000, "Failed getline () calls do not keep the fields"); + + $r0 = rss (); + for (1 .. 40) { + $csv->getline_all (do { open my $fh, "<", \$good or die "$!"; $fh }); + } + $grow = rss () - $r0; + cmp_ok ($grow, "<", 8_000_000, "getline_all () does not keep the trailing row"); + + $r0 = rss (); + my $out = ""; + open my $oh, ">", \$out or die "$!"; + $csv->print ($oh, undef) for 1 .. 200_000; + $grow = rss () - $r0; + cmp_ok ($grow, "<", 8_000_000, "print (\$fh, undef) does not keep an array"); + } +else { + ok (1, "This test only runs on Linux with PerlIO") for 1..4; + } + +1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/t/65_allow.t new/Text-CSV_XS-1.65/t/65_allow.t --- old/Text-CSV_XS-1.64/t/65_allow.t 2022-09-13 09:37:35.000000000 +0200 +++ new/Text-CSV_XS-1.65/t/65_allow.t 2026-10-14 15:24:34.000000000 +0200 @@ -4,7 +4,7 @@ use warnings; #use Test::More "no_plan"; - use Test::More tests => 1119; + use Test::More tests => 1126; BEGIN { use_ok "Text::CSV_XS", (); @@ -425,3 +425,30 @@ is ( $csv->parse ($str), 1, "Parse trailing ESC"); is_deeply ([ $csv->fields ], [ 1,3,"\0" ], "Parse passed"); } + +{ # An unquoted field of only blanks under allow_whitespace is stripped + # to empty without looking before the start of the field. The out of + # bounds access this guards against shows under valgrind or ASan only. + my $csv = Text::CSV_XS->new ({ binary => 1, allow_whitespace => 1, escape_char => "\\" }); + ok ($csv->parse ("\\ ,x"), "parse escaped blanks"); + is_deeply ([ $csv->fields ], [ "", "x" ], "Blank field stripped to empty"); + + ok ($csv->parse ("\\ \t \t,x"), "parse escaped blanks and tabs"); + is_deeply ([ $csv->fields ], [ "", "x" ], "Blank and tab field stripped to empty"); + + use Config; + if (exists $Config{useperlio} && + defined $Config{useperlio} && + $Config{useperlio} eq "define") { + $csv = Text::CSV_XS->new ({ binary => 1, allow_whitespace => 1 }); + open my $fh, "<", \qq{" "\r\r\nx,y\n} or die "$!"; + ok ($csv->getline ($fh), "getline CR CR auto-detection"); + ok ($csv->parse ("a , b "), "Trailing blanks on non-blank fields"); + is_deeply ([ $csv->fields ], [ "a", "b" ], "Stripped"); + } + else { + ok (1, "Test skipped: no PerlIO"); + ok (1, "Test skipped: no PerlIO"); + ok (1, "Test skipped: no PerlIO"); + } + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/t/67_emptrow.t new/Text-CSV_XS-1.65/t/67_emptrow.t --- old/Text-CSV_XS-1.64/t/67_emptrow.t 2026-05-03 10:00:18.000000000 +0200 +++ new/Text-CSV_XS-1.65/t/67_emptrow.t 2026-10-14 17:05:16.000000000 +0200 @@ -3,10 +3,11 @@ use strict; use warnings; +use Config; use Test::More; BEGIN { - plan tests => 58; + plan tests => 66; use_ok "Text::CSV_XS", ("csv"); plan skip_all => "Cannot load Text::CSV_XS" if $@; @@ -142,3 +143,31 @@ isnt ($row->{a}, undef, "Column 'a' is defined"); } close $fh; + +if (exists $Config{useperlio} && + defined $Config{useperlio} && + $Config{useperlio} eq "define") { + # Skipping an empty row must not release the caller's bound scalar + foreach my $t ( + [ 2, "1,2\n\n3,4\n", 2 ], # eof: stops at the empty row + [ 1, "1,2\n\n", 1 ], # skip: trailing empty line + [ 1, "1,2\n\n3,4\n", 2 ], # skip: empty row mid-stream + [ 0, "1,2\n3,4\n", 2 ], # no empty row at all + ) { + my ($ser, $data, $rows) = @$t; + open my $fh, "<", \$data or die "$!"; + my $csv = Text::CSV_XS->new ({ binary => 1, skip_empty_rows => $ser }); + my ($x, $y); + $csv->bind_columns (\$x, \$y); + my $before = Internals::SvREFCNT ($x); + my $n = 0; + $n++ while $csv->getline ($fh); + is ($n, $rows, "skip_empty_rows $ser: rows"); + is (Internals::SvREFCNT ($x), $before, "skip_empty_rows $ser: bound scalar keeps its reference"); + } + } +else { + ok (1, "No PerlIO available for this test") for 1..8; + } + +1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/t/78_fragment.t new/Text-CSV_XS-1.65/t/78_fragment.t --- old/Text-CSV_XS-1.64/t/78_fragment.t 2026-05-03 10:14:28.000000000 +0200 +++ new/Text-CSV_XS-1.65/t/78_fragment.t 2026-10-14 17:40:10.000000000 +0200 @@ -14,7 +14,7 @@ plan skip_all => "No reliable perlIO available"; } else { - plan tests => 38; + plan tests => 48; } } @@ -109,6 +109,39 @@ is (0 + $csv->error_diag, 2013, "Error in spec"); } +{ my $data = "11,12,13\n21,22,23\n31,32,33\n"; + + sub frag { + my $spec = shift; + open my $io, "<", \$data or die "$!"; + Text::CSV_XS->new ()->fragment ($io, $spec); + } # frag + + # Selector integers beyond the data select nothing extra + is_deeply (frag ("row=2-9"), [[21,22,23],[31,32,33]], "row range past the data"); + is_deeply (frag ("col=3-9"), [[13],[23],[33]], "col range past the data"); + is_deeply (frag ("cell=1,2-2,9"), [[12,13],[22,23]], "cell range past the data"); + is_deeply (frag ("cell=1,5"), [[undef]], "single cell past the data"); + + # Existing semantics of open-ended and mixed ranges are unchanged + is_deeply (frag ("row=2-*"), [[21,22,23],[31,32,33]], "row=2-*"); + is_deeply (frag ("row=3-*;1"), [[11,12,13],[31,32,33]], "row=3-*;1"); + is_deeply (frag ("col=1;3-*"), [[11,13],[21,23],[31,33]], "col=1;3-*"); + is_deeply (frag ("col=2-2"), [[12],[22],[32]], "col=2-2"); + + # The memory used depends on the data, not on the selector + SKIP: { + $^O eq "MSWin32" || !-x "/bin/sh" and skip "needs a POSIX shell for ulimit", 2; + my @inc = map { "-I$_" } @INC; + for my $spec ("row=1-400000000", "cell=1,1-1,400000000") { + my $prog = q{open my $io, "<", \"1,2\n3,4\n" or die; } + . q{Text::CSV_XS->new->fragment ($io, $ARGV[0]); print "ok\n"}; + my $out = qx{/bin/sh -c 'ulimit -v 524288 2>/dev/null; exec "\$0" "\$@"' $^X @inc -MText::CSV_XS -e '$prog' '$spec' 2>&1}; + like ($out, qr{^ok$}m, "$spec on a tiny CSV within 512 MiB"); + } + } + } + #$csv->eol ("\n"); #foreach my $r (1..9){$csv->print(*STDOUT,[map{$r.$_}1..9])} __END__ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Text-CSV_XS-1.64/t/97_parse_eof.t new/Text-CSV_XS-1.65/t/97_parse_eof.t --- old/Text-CSV_XS-1.64/t/97_parse_eof.t 1970-01-01 01:00:00.000000000 +0100 +++ new/Text-CSV_XS-1.65/t/97_parse_eof.t 2026-10-14 14:37:47.000000000 +0200 @@ -0,0 +1,29 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 12; + +use Text::CSV_XS; + +# A CR at the very end of parse () input is not part of an EOL, also +# when the last field is empty: report it, do not turn EOF into data +foreach my $bin (0, 1) { + my $csv = Text::CSV_XS->new ({ binary => $bin }); + ok (!$csv->parse ("a,\r"), "binary $bin: a,CR fails"); + is (($csv->error_diag)[0], 2032, "binary $bin: a,CR is 2032"); + ok (!$csv->parse ("a,b\r"), "binary $bin: a,b,CR fails"); + is (($csv->error_diag)[0], 2032, "binary $bin: same as non-empty field"); + } + +my $s = "a,\r"; +utf8::upgrade ($s); +my $csv = Text::CSV_XS->new (); +ok (!$csv->parse ($s), "UTF-8 upgraded a,CR fails"); +is (($csv->error_diag)[0], 2032, "UTF-8 upgraded a,CR is 2032"); + +# Unchanged neighbours +$csv = Text::CSV_XS->new ({ binary => 1 }); +ok ($csv->parse ("a,\r\n"), "parse a,CRLF"); +is_deeply ([ $csv->fields ], [ "a", "" ], "a,CRLF"); ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.1vDgs9/_old 2026-10-21 20:51:52.678855519 +0200 +++ /var/tmp/diff_new_pack.1vDgs9/_new 2026-10-21 20:51:52.685855811 +0200 @@ -1,6 +1,6 @@ -mtime: 1782204035 -commit: dd9e6bae88885ec41e68c1b33573872143c2a21d0d5273919cfd4fbae255594f +mtime: 1792505111 +commit: ec79b0b4fb5e0723f4b5466e39b313bf9f7401b3a582dd2f1138f5c3fd116dad url: https://src.opensuse.org/perl/perl-Text-CSV_XS -revision: dd9e6bae88885ec41e68c1b33573872143c2a21d0d5273919cfd4fbae255594f +revision: ec79b0b4fb5e0723f4b5466e39b313bf9f7401b3a582dd2f1138f5c3fd116dad projectscmsync: https://src.opensuse.org/perl/_ObsPrj ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-10-20 16:05:11.000000000 +0200 @@ -0,0 +1 @@ +.osc
