Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package perl-Spreadsheet-XLSX for 
openSUSE:Factory checked in at 2026-10-30 18:18:27
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/perl-Spreadsheet-XLSX (Old)
 and      /work/SRC/openSUSE:Factory/.perl-Spreadsheet-XLSX.new.1255 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "perl-Spreadsheet-XLSX"

Fri Oct 30 18:18:27 2026 rev:13 rq:1393888 version:0.200.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/perl-Spreadsheet-XLSX/perl-Spreadsheet-XLSX.changes  
    2024-04-07 22:13:44.759610253 +0200
+++ 
/work/SRC/openSUSE:Factory/.perl-Spreadsheet-XLSX.new.1255/perl-Spreadsheet-XLSX.changes
    2026-10-30 18:24:11.099118302 +0100
@@ -1,0 +2,23 @@
+Fri Oct 30 11:29:45 UTC 2026 - Tina Müller <[email protected]>
+
+- updated to 0.200.0 (0.20)
+   see /usr/share/doc/packages/perl-Spreadsheet-XLSX/Changes
+
+  0.20  2026-10-29
+      - Security: limit ZIP entry counts and uncompressed sizes; read members
+        incrementally and reject inconsistent size metadata.
+      - Security: validate number format IDs and escape regex lookups to 
prevent
+        regex injection from crafted styles.
+      - Security: reject out-of-range date values to prevent excessive CPU 
usage.
+      - Security: validate cell references against XLSX worksheet limits before
+        indexing cell arrays, preventing excessive allocation from invalid 
rows.
+      - Add cpanfile for easy dependency installation.
+      - Improve Readme.
+  0.19  2026-10-29
+      - Security: replace string eval of number-format conditions with strict
+        numeric comparisons; invalid conditions use the fallback format 
section.
+        CVE-2026-115943 bsc#1288922
+        Prevents arbitrary Perl code execution when parsing crafted XLSX files.
+      - Add regression tests for numeric conditions and malicious XLSX formats.
+
+-------------------------------------------------------------------

Old:
----
  Spreadsheet-XLSX-0.18.tar.gz

New:
----
  README.md
  Spreadsheet-XLSX-0.20.tar.gz
  _scmsync.obsinfo
  build.specials.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ perl-Spreadsheet-XLSX.spec ++++++
--- /var/tmp/diff_new_pack.MxeWW3/_old  2026-10-30 18:24:12.078159287 +0100
+++ /var/tmp/diff_new_pack.MxeWW3/_new  2026-10-30 18:24:12.080159371 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package perl-Spreadsheet-XLSX
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,23 +18,24 @@
 
 %define cpan_name Spreadsheet-XLSX
 Name:           perl-Spreadsheet-XLSX
-Version:        0.180.0
+Version:        0.200.0
 Release:        0
-# 0.18 -> normalize -> 0.180.0
-%define cpan_version 0.18
+# 0.20 -> normalize -> 0.200.0
+%define cpan_version 0.20
 License:        Artistic-1.0 OR GPL-1.0-or-later
 Summary:        Perl extension for reading MS Excel 2007 files
 URL:            https://metacpan.org/release/%{cpan_name}
 Source0:        
https://cpan.metacpan.org/authors/id/A/AS/ASB/%{cpan_name}-%{cpan_version}.tar.gz
 Source1:        cpanspec.yml
+Source100:      README.md
 BuildArch:      noarch
 BuildRequires:  perl
 BuildRequires:  perl-macros
-BuildRequires:  perl(Archive::Zip) >= 1.18
+BuildRequires:  perl(Archive::Zip) >= 1.180
 BuildRequires:  perl(Spreadsheet::ParseExcel)
 BuildRequires:  perl(Test::NoWarnings)
 BuildRequires:  perl(Test::Warnings)
-Requires:       perl(Archive::Zip) >= 1.18
+Requires:       perl(Archive::Zip) >= 1.180
 Requires:       perl(Spreadsheet::ParseExcel)
 Provides:       perl(Spreadsheet::XLSX) = %{version}
 Provides:       perl(Spreadsheet::XLSX::Fmt2007) = %{version}
@@ -43,13 +44,30 @@
 %{perl_requires}
 
 %description
+ZIP input is limited to 10,000 entries, 64 MiB per uncompressed member, and
+256 MiB of total declared uncompressed data, including unused members.
+Members used by the parser are decompressed incrementally and their actual
+sizes are checked against the limits and ZIP metadata. Exceeding a limit or
+encountering inconsistent sizes raises an exception.
+
+Applications needing different limits can scope them to a parsing
+operation:
+
+    local $Spreadsheet::XLSX::MAX_MEMBER_SIZE = 128 * 1024 * 1024;
+    local $Spreadsheet::XLSX::MAX_TOTAL_SIZE = 512 * 1024 * 1024;
+    local $Spreadsheet::XLSX::MAX_MEMBERS = 20000;
+    my $excel = Spreadsheet::XLSX->new($filename);
+
+These limits bound uncompressed input, not the total memory used by parsed
+cells and XML processing.
+
 This module is a (quick and dirty) emulation of Spreadsheet::ParseExcel for
 Excel 2007 (.xlsx) file format. It supports styles and many of Excel's
 quirks, but not all. It populates the classes from Spreadsheet::ParseExcel
 for interoperability; including Workbook, Worksheet, and Cell.
 
 %prep
-%autosetup  -n %{cpan_name}-%{cpan_version}
+%autosetup -n %{cpan_name}-%{cpan_version} -p1
 
 %build
 perl Makefile.PL INSTALLDIRS=vendor

++++++ README.md ++++++

## Build Results

Current state of perl in openSUSE:Factory is

![Factory build 
results](https://br.opensuse.org/status/openSUSE:Factory/perl-Spreadsheet-XLSX/standard)

The current state of perl in the devel project build (devel:languages:perl)

![Devel project build 
results](https://br.opensuse.org/status/devel:languages:perl/perl-Spreadsheet-XLSX)



++++++ Spreadsheet-XLSX-0.18.tar.gz -> Spreadsheet-XLSX-0.20.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/Changes 
new/Spreadsheet-XLSX-0.20/Changes
--- old/Spreadsheet-XLSX-0.18/Changes   2024-03-09 21:08:39.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/Changes   2026-10-29 22:18:37.000000000 +0100
@@ -1,5 +1,24 @@
 Revision history for Perl extension Spreadsheet::XLSX.
 
+0.20  2026-10-29
+
+    - Security: limit ZIP entry counts and uncompressed sizes; read members
+      incrementally and reject inconsistent size metadata.
+    - Security: validate number format IDs and escape regex lookups to prevent
+      regex injection from crafted styles.
+    - Security: reject out-of-range date values to prevent excessive CPU usage.
+    - Security: validate cell references against XLSX worksheet limits before
+      indexing cell arrays, preventing excessive allocation from invalid rows.
+    - Add cpanfile for easy dependency installation.
+    - Improve Readme.
+
+0.19  2026-10-29
+
+    - Security: replace string eval of number-format conditions with strict
+      numeric comparisons; invalid conditions use the fallback format section.
+      Prevents arbitrary Perl code execution when parsing crafted XLSX files.
+    - Add regression tests for numeric conditions and malicious XLSX formats.
+
 0.18
 
     - fix RT #117166: unformatted date values can no longer be retrieved, thx 
to lherschi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/MANIFEST 
new/Spreadsheet-XLSX-0.20/MANIFEST
--- old/Spreadsheet-XLSX-0.18/MANIFEST  2023-12-31 14:23:06.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/MANIFEST  2026-10-29 22:13:04.000000000 +0100
@@ -1,4 +1,5 @@
 Changes
+cpanfile
 lib/Spreadsheet/XLSX.pm
 lib/Spreadsheet/XLSX/Fmt2007.pm
 lib/Spreadsheet/XLSX/Utility2007.pm
@@ -16,6 +17,11 @@
 t/empty_v_tag.t
 t/empty_v_tag.xlsx
 t/formats.t
+t/format_conditions.t
+t/cell_references.t
+t/date_limits.t
+t/number_format_ids.t
+t/zip_limits.t
 t/formats.xlsx
 t/kwalitee.t
 t/missing_styles.t
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/META.json 
new/Spreadsheet-XLSX-0.20/META.json
--- old/Spreadsheet-XLSX-0.18/META.json 2024-03-09 21:13:23.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/META.json 2026-10-29 22:20:02.000000000 +0100
@@ -51,6 +51,6 @@
          "url" : "https://github.com/asb-capfan/Spreadsheet-XLSX";
       }
    },
-   "version" : "0.18",
+   "version" : "0.20",
    "x_serialization_backend" : "JSON::PP version 4.04"
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/META.yml 
new/Spreadsheet-XLSX-0.20/META.yml
--- old/Spreadsheet-XLSX-0.18/META.yml  2024-03-09 21:13:21.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/META.yml  2026-10-29 22:20:00.000000000 +0100
@@ -26,5 +26,5 @@
   Spreadsheet::ParseExcel: '0'
 resources:
   repository: https://github.com/asb-capfan/Spreadsheet-XLSX
-version: '0.18'
+version: '0.20'
 x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/README 
new/Spreadsheet-XLSX-0.20/README
--- old/Spreadsheet-XLSX-0.18/README    2023-12-31 14:22:41.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/README    2026-10-29 22:17:37.000000000 +0100
@@ -14,22 +14,82 @@
 
 DEPENDENCIES
 
-This module requires these other modules and libraries:
+Dependencies are listed in cpanfile. From the repository root, install the
+runtime and test dependencies with cpanminus:
 
-  Archive::Zip
-  Spreadsheet::ParseExcel
+   cpanm --installdeps .
+
+For development and release testing, include the develop dependencies:
+
+   cpanm --installdeps --with-develop .
+
+If cpanm is not installed, install it first:
+
+   cpan App::cpanminus
 
 BUILD AND RELEASE INSTRUCTIONS
 
-Note: Meta files (META.json and META.yml) have to be generated using 
ExtUtils::MakeMaker to satisfy the MANIFEST file. 
+Run the following commands from the root of the checked-out repository.
+On Windows with Strawberry Perl, use gmake instead of make throughout.
+Building the .tar.gz archive also requires tar and gzip on PATH.
+
+1. Prepare the release.
+
+   Update the version in lib/Spreadsheet/XLSX.pm,
+   lib/Spreadsheet/XLSX/Fmt2007.pm and lib/Spreadsheet/XLSX/Utility2007.pm.
+   Update Changes with the release version and date, and add any new
+   distribution files to MANIFEST.
+
+2. Install dependencies and generate the Makefile.
+
+   Install all dependencies from cpanfile, including release test modules:
+
+      cpanm --installdeps --with-develop .
+      perl Makefile.PL
+
+   Test::Kwalitee and Test::Warnings are required to run the release-only
+   tests; those tests are skipped if the modules are missing.
+
+3. Run the regular tests from the checkout.
+
+      prove -lr t
+
+4. Build a distribution directory and run all tests there.
+
+   In a POSIX shell:
+
+      RELEASE_TESTING=1 make disttest
+
+   In PowerShell with Strawberry Perl:
+
+      $env:RELEASE_TESTING = '1'
+      make disttest
+      Remove-Item Env:RELEASE_TESTING
+
+   Check that all tests pass, including the release-only tests.
+   disttest creates the distribution directory and generates META.json and
+   META.yml before testing. These files need not exist in the checkout;
+   running the release-only tests directly there can therefore report a
+   missing META.yml. An initial Makefile.PL warning about missing META files
+   is expected in a fresh checkout.
+
+5. Create the release archive after the tests pass.
+
+      make dist
+
+   This creates Spreadsheet-XLSX-<version>.tar.gz, including META.json and
+   META.yml. Building the archive does not upload or publish the release.
+
+6. Remove temporary build files.
+
+      make realclean
 
-Use the commands:
+   realclean removes build files, blib, MYMETA.json, MYMETA.yml, the Makefile
+   and the generated distribution directory. The release .tar.gz archive
+   remains available for upload. make clean performs a partial cleanup
+   and leaves Makefile.old behind.
 
-   perl Makefile.PL
-   make distdir
-   make disttest
-   make dist
-   
+   To build again after realclean, start with perl Makefile.PL.
 
 COPYRIGHT AND LICENCE
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/cpanfile 
new/Spreadsheet-XLSX-0.20/cpanfile
--- old/Spreadsheet-XLSX-0.18/cpanfile  1970-01-01 01:00:00.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/cpanfile  2026-10-29 22:13:04.000000000 +0100
@@ -0,0 +1,18 @@
+requires 'perl', '5.006000';
+requires 'Archive::Zip', '1.18';
+requires 'Spreadsheet::ParseExcel';
+
+on 'configure' => sub {
+    requires 'ExtUtils::MakeMaker';
+};
+
+on 'test' => sub {
+    requires 'Test::More';
+    requires 'Test::Warnings';
+    requires 'Test::NoWarnings';
+    requires 'File::Temp';
+};
+
+on 'develop' => sub {
+    requires 'Test::Kwalitee';
+};
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/Spreadsheet-XLSX-0.18/lib/Spreadsheet/XLSX/Fmt2007.pm 
new/Spreadsheet-XLSX-0.20/lib/Spreadsheet/XLSX/Fmt2007.pm
--- old/Spreadsheet-XLSX-0.18/lib/Spreadsheet/XLSX/Fmt2007.pm   2024-03-09 
21:12:16.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/lib/Spreadsheet/XLSX/Fmt2007.pm   2026-10-29 
22:09:41.000000000 +0100
@@ -3,7 +3,7 @@
 use warnings;
 
 use Spreadsheet::XLSX::Utility2007 qw(ExcelFmt);
-our $VERSION = '0.18';    #
+our $VERSION = '0.20';    #
 
 my %hFmtDefault = (
     0x00 => '@',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/Spreadsheet-XLSX-0.18/lib/Spreadsheet/XLSX/Utility2007.pm 
new/Spreadsheet-XLSX-0.20/lib/Spreadsheet/XLSX/Utility2007.pm
--- old/Spreadsheet-XLSX-0.18/lib/Spreadsheet/XLSX/Utility2007.pm       
2024-03-09 21:12:22.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/lib/Spreadsheet/XLSX/Utility2007.pm       
2026-10-29 22:09:35.000000000 +0100
@@ -13,13 +13,28 @@
 use vars qw(@ISA @EXPORT_OK);
 @ISA       = qw(Exporter);
 @EXPORT_OK = qw(ExcelFmt LocaltimeExcel ExcelLocaltime col2int int2col 
sheetRef xls2csv);
-our $VERSION = '0.18';
+our $VERSION = '0.20';
 
 my $sNUMEXP = '(^[+-]?\d+(\.\d+)?$)|(^[+-]?\d+\.?(\d*)[eE][+-](\d+))$';
 
 #------------------------------------------------------------------------------
 # ExcelFmt (for Spreadsheet::XLSX::Utility2007)
 #------------------------------------------------------------------------------
+# Treat format conditions as data. Invalid conditions select the fallback 
section.
+sub _matches_condition {
+    my ($value, $condition) = @_;
+    my $number = qr/[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?/;
+    return 0 unless defined($value) && $value =~ /\A\s*$number\s*\z/;
+    return 0 unless $condition =~ /\A\s*(>=|<=|<>|==|>|<|=)\s*($number)\s*\z/;
+    my ($operator, $operand) = ($1, $2);
+    return $value >  $operand if $operator eq '>';
+    return $value >= $operand if $operator eq '>=';
+    return $value <  $operand if $operator eq '<';
+    return $value <= $operand if $operator eq '<=';
+    return $value != $operand if $operator eq '<>';
+    return $value == $operand;
+}
+
 sub ExcelFmt {
     my ($sFmt, $iData, $i1904, $sType) = @_;
     my $sCond;
@@ -69,7 +84,7 @@
     #Get FmtString
     if (scalar(@sFmtWk) > 1) {
         if ($sCond) {
-            $sFmtObj = $sFmtWk[((eval(qq/"$iData" $sCond/)) ? 0 : 1)];
+            $sFmtObj = $sFmtWk[(_matches_condition($iData, $sCond) ? 0 : 1)];
         } else {
             my $iWk = ($iData =~ /$sNUMEXP/) ? $iData : 0;
 
@@ -697,6 +712,14 @@
 #------------------------------------------------------------------------------
 sub ExcelLocaltime {
     my ($dObj, $flg1904) = @_;
+    # Excel dates end on 9999-12-31; allow the fractional time on that day.
+    # Check before int() and the year loop, including numbers that overflow
+    # to infinity when converted from scientific notation.
+    my $limit = $flg1904 ? 2957004 : 2958466;
+    die "Invalid or out-of-range XLSX date value\n"
+        unless defined($dObj)
+        && $dObj =~ 
/\A[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?\z/
+        && $dObj >= 0 && $dObj < $limit;
     my ($iSec, $iMin, $iHour, $iDay, $iMon, $iYear, $iwDay, $iMSec);
     my ($iDt, $iTime, $iYDays);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/lib/Spreadsheet/XLSX.pm 
new/Spreadsheet-XLSX-0.20/lib/Spreadsheet/XLSX.pm
--- old/Spreadsheet-XLSX-0.18/lib/Spreadsheet/XLSX.pm   2024-03-09 
21:12:01.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/lib/Spreadsheet/XLSX.pm   2026-10-29 
22:09:26.000000000 +0100
@@ -6,7 +6,11 @@
 
 use base 'Spreadsheet::ParseExcel::Workbook';
 
-our $VERSION = '0.18';
+our $VERSION = '0.20';
+
+our $MAX_MEMBER_SIZE = 64 * 1024 * 1024;
+our $MAX_TOTAL_SIZE  = 256 * 1024 * 1024;
+our $MAX_MEMBERS     = 10000;
 
 use Archive::Zip;
 use Spreadsheet::ParseExcel;
@@ -42,11 +46,12 @@
     $self->{SheetCount} = 0;
     $self->{FmtClass}   = Spreadsheet::XLSX::Fmt2007->new;
     $self->{Flg1904}    = 0;
-    if ($member_workbook->contents =~ /date1904="1"/) {
+    my $workbook_xml = __member_contents($member_workbook);
+    if ($workbook_xml =~ /date1904="1"/) {
         $self->{Flg1904} = 1;
     }
 
-    foreach ($member_workbook->contents =~ /\<(.*?)\/?\>/g) {
+    foreach ($workbook_xml =~ /\<(.*?)\/?\>/g) {
 
         /^(\w+)\s+/;
 
@@ -95,10 +100,12 @@
         my $s    = 0;
         my $s2   = 0;
         my $sty  = 0;
-        foreach ($member_sheet->contents =~ /(\<.*?\/?\>|.*?(?=\<))/g) {
-            if (/^\<c\s*.*?\s*r=\"([A-Z])([A-Z]?)(\d+)\"/) {
+        my $sheet_xml = __member_contents($member_sheet);
+        foreach ($sheet_xml =~ /(\<.*?\/?\>|.*?(?=\<))/g) {
+            if (/^<c(?:\s|\/?>)/) {
 
-                ($row, $col) = __decode_cell_name($1, $2, $3);
+                my ($reference) = /\sr="([^"]*)"/;
+                ($row, $col) = __decode_cell_name($reference);
 
                 $s   = m/t=\"s\"/      ? 1  : 0;
                 $s2  = m/t=\"str\"/    ? 1  : 0;
@@ -163,22 +170,22 @@
     return $self;
 }
 
-# Convert cell name in the format AA1 to a row and column number.
+# Validate before numeric conversion or using a reference as an array index.
+# XLSX worksheets have at most 1,048,576 rows and 16,384 columns (XFD).
 
 sub __decode_cell_name {
-    my ($letter1, $letter2, $digits) = @_;
-
-    my $col = ord($letter1) - 65;
-
-    if ($letter2) {
-        $col++;
-        $col *= 26;
-        $col += (ord($letter2) - 65);
-    }
+    my ($reference) = @_;
+    die "Invalid XLSX cell reference\n"
+        unless defined($reference)
+        && $reference =~ /\A([A-Z]{1,3})([1-9][0-9]{0,6})\z/;
+    my ($letters, $digits) = ($1, $2);
+    die "XLSX cell row exceeds worksheet limit\n" if $digits > 1048576;
+
+    my $col = 0;
+    $col = $col * 26 + ord($_) - ord('A') + 1 for split //, $letters;
+    die "XLSX cell column exceeds worksheet limit\n" if $col > 16384;
 
-    my $row = $digits - 1;
-
-    return ($row, $col);
+    return ($digits - 1, $col - 1);
 }
 
 
@@ -191,7 +198,7 @@
 
     if ($member_shared_strings) {
 
-        my $mstr = $member_shared_strings->contents;
+        my $mstr = __member_contents($member_shared_strings);
         $mstr =~ s/<t\/>/<t><\/t>/gsm;    # this handles an empty t tag in the 
xml <t/>
         foreach my $si ($mstr =~ /<si.*?>(.*?)<\/si/gsm) {
             my $str;
@@ -217,15 +224,22 @@
 
     if ($member_styles) {
         my $formatter = Spreadsheet::XLSX::Fmt2007->new();
+        my $styles_xml = __member_contents($member_styles);
+
+        # IDs from both format declarations and style references are data,
+        # never regular-expression syntax.
+        foreach my $id ($styles_xml =~ /\bnumFmtId="([^"]*)"/g) {
+            die "Invalid XLSX number format ID\n" unless $id =~ /\A[0-9]+\z/;
+        }
 
-        foreach my $t ($member_styles->contents =~ /xf\ 
numFmtId="([^"]*)"(?!.*\/cellStyleXfs)/gsm) {    #"
+        foreach my $t ($styles_xml =~ /xf\ 
numFmtId="([^"]*)"(?!.*\/cellStyleXfs)/gsm) {    #"
             push @styles, $t;
         }
 
         my $default = $1 || '';
     
         foreach my $t1 (@styles) {
-            $member_styles->contents =~ /numFmtId="$t1" formatCode="([^"]*)/;
+            $styles_xml =~ /numFmtId="\Q$t1\E" formatCode="([^"]*)/;
             my $formatCode = $1 || '';
             if ($formatCode eq $default || not($formatCode)) {
                 if ($t1 == 9 || $t1 == 10) {
@@ -253,7 +267,8 @@
 
     my %rels = ();
 
-    foreach ($member_rels->contents =~ /\<Relationship (.*?)\/?\>/g) {
+    my $rels_xml = __member_contents($member_rels);
+    foreach ($rels_xml =~ /\<Relationship (.*?)\/?\>/g) {
 
         my ($id, $target);
         ($id) = /Id="(.*?)"/;
@@ -279,9 +294,50 @@
         $zip->read($filename) == Archive::Zip::AZ_OK or die("Cannot open 
$filename as Zip archive");
     }
     
+    my @members = $zip->members();
+    die "XLSX ZIP entry count exceeds limit\n" if @members > $MAX_MEMBERS;
+    my $total = 0;
+    foreach my $member (@members) {
+        my $size = $member->uncompressedSize();
+        die "XLSX ZIP member size exceeds limit\n" if $size > $MAX_MEMBER_SIZE;
+        $total += $size;
+        die "XLSX ZIP total size exceeds limit\n" if $total > $MAX_TOTAL_SIZE;
+    }
+
     return $zip;
 }
 
+# readChunk() limits compressed input, not inflated output. Small input chunks
+# bound temporary inflate buffers; check output before appending it. Never 
trust
+# central-directory sizes alone: actual output must not exceed the declared 
size.
+sub __member_contents {
+    my ($member) = @_;
+    my $declared = $member->uncompressedSize();
+    die "XLSX ZIP member size exceeds limit\n" if $declared > $MAX_MEMBER_SIZE;
+    my $old_method = 
$member->desiredCompressionMethod(Archive::Zip::COMPRESSION_STORED);
+    my $contents = '';
+    my $ok = eval {
+        die "Cannot read XLSX ZIP member\n" unless $member->rewindData() == 
Archive::Zip::AZ_OK;
+        while (1) {
+            my ($buffer, $status) = $member->readChunk(1024);
+            die "Cannot read XLSX ZIP member\n"
+                unless $status == Archive::Zip::AZ_OK || $status == 
Archive::Zip::AZ_STREAM_END;
+            my $size = length($contents) + length($$buffer);
+            die "XLSX ZIP member size exceeds limit\n" if $size > 
$MAX_MEMBER_SIZE;
+            die "XLSX ZIP member size does not match metadata\n" if $size > 
$declared;
+            $contents .= $$buffer;
+            last if $status == Archive::Zip::AZ_STREAM_END;
+        }
+        die "XLSX ZIP member size does not match metadata\n" if 
length($contents) != $declared;
+        1;
+    };
+    my $error = $@;
+    $member->endRead();
+    $member->desiredCompressionMethod($old_method);
+    die $error unless $ok;
+    return $contents;
+}
+
 
 1;
 __END__
@@ -328,6 +384,22 @@
 
 =head1 DESCRIPTION
 
+ZIP input is limited to 10,000 entries, 64 MiB per uncompressed member, and
+256 MiB of total declared uncompressed data, including unused members.
+Members used by the parser are decompressed incrementally and their actual
+sizes are checked against the limits and ZIP metadata. Exceeding a limit or
+encountering inconsistent sizes raises an exception.
+
+Applications needing different limits can scope them to a parsing operation:
+
+    local $Spreadsheet::XLSX::MAX_MEMBER_SIZE = 128 * 1024 * 1024;
+    local $Spreadsheet::XLSX::MAX_TOTAL_SIZE = 512 * 1024 * 1024;
+    local $Spreadsheet::XLSX::MAX_MEMBERS = 20000;
+    my $excel = Spreadsheet::XLSX->new($filename);
+
+These limits bound uncompressed input, not the total memory used by parsed
+cells and XML processing.
+
 This module is a (quick and dirty) emulation of L<Spreadsheet::ParseExcel> for 
 Excel 2007 (.xlsx) file format.  It supports styles and many of Excel's 
quirks, 
 but not all.  It populates the classes from L<Spreadsheet::ParseExcel> for 
interoperability; 
@@ -339,7 +411,7 @@
 
 =item L<Spreadsheet::ParseXLSX>
 
-This module (Spradsheet::XLSX) has some serious issues with the way it uses 
regexs for parsing the XML.
+This module (Spreadsheet::XLSX) has some serious issues with the way it uses 
regexs for parsing the XML.
 I would strongly encourage switching to L<Spreadsheet::ParseXLSX> which takes 
a more reliable approach.
 
 =item L<Text::CSV_XS>, L<Text::CSV_PP>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/t/cell_references.t 
new/Spreadsheet-XLSX-0.20/t/cell_references.t
--- old/Spreadsheet-XLSX-0.18/t/cell_references.t       1970-01-01 
01:00:00.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/t/cell_references.t       2026-10-29 
21:57:49.000000000 +0100
@@ -0,0 +1,42 @@
+use strict;
+use warnings;
+use Test::More;
+use Archive::Zip qw(:ERROR_CODES);
+use File::Temp qw(tempfile);
+use Spreadsheet::XLSX;
+
+sub parse_reference {
+    my ($reference) = @_;
+    my %members = (
+        'xl/workbook.xml' => '<workbook xmlns:r="x"><sheets><sheet name="S1" 
sheetId="1" r:id="rId1"/></sheets></workbook>',
+        'xl/_rels/workbook.xml.rels' => '<Relationships><Relationship 
Id="rId1" Target="worksheets/sheet1.xml"/></Relationships>',
+        'xl/worksheets/sheet1.xml' => '<worksheet><sheetData><row><c 
r="A1"><v>1</v></c><c '
+            . (defined($reference) ? qq{r="$reference"} : '')
+            . '><v>2</v></c></row></sheetData></worksheet>',
+    );
+    my ($fh, $filename) = tempfile(SUFFIX => '.xlsx', UNLINK => 1);
+    close $fh;
+    my $zip = Archive::Zip->new;
+    $zip->addString($members{$_}, $_) for keys %members;
+    die 'Cannot create test workbook' unless $zip->writeToFileNamed($filename) 
== AZ_OK;
+    return Spreadsheet::XLSX->new($filename);
+}
+
+for my $reference ('A0', 'A1048577', 'A9999999', 'A' . ('9' x 1000),
+    'A01', 'A-1', 'A1junk', 'XFE1', 'AAAA1', '', undef) {
+    my $workbook = eval { parse_reference($reference) };
+    my $error = $@;
+    ok(!$workbook, 'invalid reference rejects workbook');
+    like($error, qr/(?:Invalid XLSX cell reference|XLSX cell (?:row|column) 
exceeds worksheet limit)/,
+        'controlled validation error before cell allocation');
+}
+
+for my $case (['A1', 0, 0], ['ZZ1', 0, 701], ['AAA1', 0, 702],
+    ['XFD1', 0, 16383], ['A1048576', 1048575, 0]) {
+    my ($reference, $row, $col) = @$case;
+    my $workbook = parse_reference($reference);
+    is($workbook->{Worksheet}[0]{Cells}[$row][$col]->value(), '2',
+        "valid reference $reference is decoded correctly");
+}
+
+done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/t/date_limits.t 
new/Spreadsheet-XLSX-0.20/t/date_limits.t
--- old/Spreadsheet-XLSX-0.18/t/date_limits.t   1970-01-01 01:00:00.000000000 
+0100
+++ new/Spreadsheet-XLSX-0.20/t/date_limits.t   2026-10-29 22:01:59.000000000 
+0100
@@ -0,0 +1,41 @@
+use strict;
+use warnings;
+use Test::More;
+use Archive::Zip qw(:ERROR_CODES);
+use File::Temp qw(tempfile);
+use Spreadsheet::XLSX;
+use Spreadsheet::XLSX::Utility2007 qw(ExcelFmt ExcelLocaltime);
+
+for my $epoch (0, 1) {
+    my $limit = $epoch ? 2957004 : 2958466;
+    for my $value ($limit, $limit + 1, '1e+100', '1e+9999', -1,
+        'NaN', 'Inf', 'not a number', undef) {
+        my $ok = eval { ExcelLocaltime($value, $epoch); 1 };
+        ok(!$ok, "invalid date rejected for epoch $epoch");
+        like($@, qr/Invalid or out-of-range XLSX date value/, 'controlled 
error');
+    }
+    is(ExcelFmt('yyyy-mm-dd', $limit - 1, $epoch), '9999-12-31',
+        "last supported date for epoch $epoch");
+    is(ExcelFmt('yyyy-mm-dd hh:mm:ss', $limit - 0.5, $epoch),
+        '9999-12-31 12:00:00', 'fractional time on last day is supported');
+}
+is(ExcelFmt('yyyy-mm-dd', 0, 1), '1904-01-01', '1904 epoch unchanged');
+is(ExcelFmt('yyyy-mm-dd', 60, 0), '1900-02-29', 'Excel leap-year 
compatibility');
+is(ExcelFmt('yyyy-mm-dd', 61, 0), '1900-03-01', 'date after Excel leap day');
+
+# Verify that an extreme numeric cell is rejected through the public parser.
+my %members = (
+    'xl/workbook.xml' => '<workbook xmlns:r="x"><sheets><sheet name="S1" 
sheetId="1" r:id="rId1"/></sheets></workbook>',
+    'xl/_rels/workbook.xml.rels' => '<Relationships><Relationship Id="rId1" 
Target="worksheets/sheet1.xml"/></Relationships>',
+    'xl/styles.xml' => '<styleSheet><cellXfs><xf 
numFmtId="14"/></cellXfs></styleSheet>',
+    'xl/worksheets/sheet1.xml' => '<worksheet><sheetData><row><c r="A1" 
s="0"><v>1e+100</v></c></row></sheetData></worksheet>',
+);
+my ($fh, $filename) = tempfile(SUFFIX => '.xlsx', UNLINK => 1);
+close $fh;
+my $zip = Archive::Zip->new;
+$zip->addString($members{$_}, $_) for keys %members;
+is($zip->writeToFileNamed($filename), AZ_OK, 'created date regression 
workbook');
+my $ok = eval { Spreadsheet::XLSX->new($filename); 1 };
+ok(!$ok, 'parser rejects extreme date cell');
+like($@, qr/Invalid or out-of-range XLSX date value/, 'parser propagates 
controlled error');
+done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/t/format_conditions.t 
new/Spreadsheet-XLSX-0.20/t/format_conditions.t
--- old/Spreadsheet-XLSX-0.18/t/format_conditions.t     1970-01-01 
01:00:00.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/t/format_conditions.t     2026-10-29 
21:46:39.000000000 +0100
@@ -0,0 +1,48 @@
+use strict;
+use warnings;
+use Test::More;
+use Archive::Zip qw(:ERROR_CODES);
+use File::Temp qw(tempfile);
+use Spreadsheet::XLSX;
+use Spreadsheet::XLSX::Utility2007 qw(ExcelFmt);
+
+for my $case (
+    ['>2', 3, 1], ['>2', 2, 0], ['>=2', 2, 1], ['>=2', 1, 0],
+    ['<2', 1, 1], ['<2', 2, 0], ['<=2', 2, 1], ['<=2', 3, 0],
+    ['=2', 2, 1], ['=2', 3, 0], ['==2', 2, 1], ['==2', 3, 0],
+    ['<>2', 3, 1], ['<>2', 2, 0], ['> -1.5', -1, 1],
+    ['>=.5', '0.5', 1], ['=1.', 1, 1], ['=+1e2', 100, 1],
+    ['=1E-2', '0.01', 1],
+) {
+    my ($condition, $value, $matches) = @$case;
+    is(ExcelFmt("[$condition]0.0;0.00", $value),
+        sprintf($matches ? '%.1f' : '%.2f', $value), "$value compared with 
$condition");
+}
+
+our $executed = 0;
+my $payload = '>0 and do { $main::executed = 1 }';
+for my $condition ($payload, '>0 trailing', '=>1', '>1e', '>NaN', '>1\n') {
+    is(ExcelFmt("[$condition]0.0;0.00", 1), '1.00',
+        "invalid condition uses fallback: $condition");
+}
+is($executed, 0, 'format condition cannot execute Perl');
+ExcelFmt('[>0]"yes";"no"', '1"; $main::executed = 1; #');
+is($executed, 0, 'cell value cannot execute Perl');
+
+# Exercise the public parser path with a crafted workbook, without OS commands.
+my %members = (
+    'xl/workbook.xml' => '<workbook xmlns:r="x"><sheets><sheet name="S1" 
sheetId="1" r:id="rId1"/></sheets></workbook>',
+    'xl/_rels/workbook.xml.rels' => '<Relationships><Relationship Id="rId1" 
Target="worksheets/sheet1.xml"/></Relationships>',
+    'xl/styles.xml' => qq{<styleSheet><numFmts count="1"><numFmt 
numFmtId="164" formatCode="[$payload]0;0"/></numFmts><cellXfs count="1"><xf 
numFmtId="164" fontId="0" fillId="0" borderId="0"/></cellXfs></styleSheet>},
+    'xl/worksheets/sheet1.xml' => '<worksheet><sheetData><row r="1"><c r="A1" 
s="0"><v>1</v></c></row></sheetData></worksheet>',
+);
+my ($fh, $filename) = tempfile(SUFFIX => '.xlsx', UNLINK => 1);
+close $fh;
+my $zip = Archive::Zip->new;
+$zip->addString($members{$_}, $_) for keys %members;
+is($zip->writeToFileNamed($filename), AZ_OK, 'created regression workbook');
+my $workbook = Spreadsheet::XLSX->new($filename);
+ok($workbook, 'crafted workbook parses');
+is($executed, 0, 'parsing workbook cannot execute format condition');
+is($workbook->{Worksheet}[0]{Cells}[0][0]->value(), '1', 'fallback formats 
value');
+done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/t/number_format_ids.t 
new/Spreadsheet-XLSX-0.20/t/number_format_ids.t
--- old/Spreadsheet-XLSX-0.18/t/number_format_ids.t     1970-01-01 
01:00:00.000000000 +0100
+++ new/Spreadsheet-XLSX-0.20/t/number_format_ids.t     2026-10-29 
22:03:29.000000000 +0100
@@ -0,0 +1,45 @@
+use strict;
+use warnings;
+use Test::More;
+use Archive::Zip qw(:ERROR_CODES);
+use File::Temp qw(tempfile);
+use Spreadsheet::XLSX;
+
+sub parse_style {
+    my ($reference, $declaration) = @_;
+    my %members = (
+        'xl/workbook.xml' => '<workbook xmlns:r="x"><sheets><sheet name="S1" 
sheetId="1" r:id="rId1"/></sheets></workbook>',
+        'xl/_rels/workbook.xml.rels' => '<Relationships><Relationship 
Id="rId1" Target="worksheets/sheet1.xml"/></Relationships>',
+        'xl/styles.xml' => '<styleSheet>'
+            . (defined($declaration) ? qq{<numFmts><numFmt 
numFmtId="$declaration" formatCode="0.000"/></numFmts>} : '')
+            . qq{<cellXfs><xf numFmtId="$reference"/></cellXfs></styleSheet>},
+        'xl/worksheets/sheet1.xml' => '<worksheet><sheetData><row><c r="A1" 
s="0"><v>2</v></c></row></sheetData></worksheet>',
+    );
+    my ($fh, $filename) = tempfile(SUFFIX => '.xlsx', UNLINK => 1);
+    close $fh;
+    my $zip = Archive::Zip->new;
+    $zip->addString($members{$_}, $_) for keys %members;
+    die 'Cannot create test workbook' unless $zip->writeToFileNamed($filename) 
== AZ_OK;
+    return Spreadsheet::XLSX->new($filename);
+}
+
+for my $id ('(', '.*', '164|14', '(a+)+$', '[0-9]+', '', '-1', '1.5',
+    ' 164', '164junk', '1e2') {
+    for my $location ('reference', 'declaration') {
+        my $ok = eval {
+            $location eq 'reference' ? parse_style($id, '164') : 
parse_style('164', $id);
+            1;
+        };
+        ok(!$ok, "reject invalid $location ID: $id");
+        like($@, qr/\AInvalid XLSX number format ID\n\z/,
+            'controlled validation error instead of regex compilation');
+    }
+}
+
+is(parse_style('164', '164')->{Worksheet}[0]{Cells}[0][0]->value(),
+    '2.000', 'custom numeric format ID still resolves');
+is(parse_style('0', undef)->{Worksheet}[0]{Cells}[0][0]->value(),
+    '2', 'default numeric format ID still resolves');
+is(parse_style('14', undef)->{Worksheet}[0]{Cells}[0][0]->value(),
+    '1900-01-02', 'built-in date format ID still resolves');
+done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Spreadsheet-XLSX-0.18/t/zip_limits.t 
new/Spreadsheet-XLSX-0.20/t/zip_limits.t
--- old/Spreadsheet-XLSX-0.18/t/zip_limits.t    1970-01-01 01:00:00.000000000 
+0100
+++ new/Spreadsheet-XLSX-0.20/t/zip_limits.t    2026-10-29 22:06:23.000000000 
+0100
@@ -0,0 +1,118 @@
+use strict;
+use warnings;
+use Test::More;
+use Archive::Zip qw(:ERROR_CODES :CONSTANTS);
+use File::Temp qw(tempfile);
+use Spreadsheet::XLSX;
+
+my %base = (
+    'xl/workbook.xml' => '<workbook xmlns:r="x"><sheets><sheet name="S1" 
sheetId="1" r:id="rId1"/></sheets></workbook>',
+    'xl/_rels/workbook.xml.rels' => '<Relationships><Relationship Id="rId1" 
Target="worksheets/sheet1.xml"/></Relationships>',
+    'xl/worksheets/sheet1.xml' => '<worksheet><sheetData><row><c 
r="A1"><v>2</v></c></row></sheetData></worksheet>',
+    'xl/sharedStrings.xml' => '<sst><si><t>hello</t></si></sst>',
+    'xl/styles.xml' => '<styleSheet><cellXfs><xf 
numFmtId="0"/></cellXfs></styleSheet>',
+);
+
+sub archive {
+    my ($members, $stored) = @_;
+    my ($fh, $filename) = tempfile(SUFFIX => '.xlsx', UNLINK => 1);
+    close $fh;
+    my $zip = Archive::Zip->new;
+    for my $name (sort keys %$members) {
+        my $member = $zip->addString($members->{$name}, $name);
+        $member->desiredCompressionMethod($stored ? COMPRESSION_STORED : 
COMPRESSION_DEFLATED);
+    }
+    die 'Cannot create test archive' unless $zip->writeToFileNamed($filename) 
== AZ_OK;
+    return $filename;
+}
+
+sub rejects {
+    my ($filename, $pattern, $description) = @_;
+    my $ok = eval { Spreadsheet::XLSX->new($filename); 1 };
+    my $error = $@;
+    ok(!$ok, $description);
+    like($error, $pattern, 'controlled ZIP validation error');
+}
+
+for my $stored (0, 1) {
+    my $filename = archive(\%base, $stored);
+    is(Spreadsheet::XLSX->new($filename)->{Worksheet}[0]{Cells}[0][0]->value(),
+        '2', 'ordinary workbook parses');
+    open my $fh, '<', $filename or die $!;
+    binmode $fh;
+    is(Spreadsheet::XLSX->new($fh)->{Worksheet}[0]{Cells}[0][0]->value(),
+        '2', 'filehandle input parses');
+    close $fh;
+}
+
+my $filename = archive(\%base);
+{
+    local $Spreadsheet::XLSX::MAX_MEMBERS = scalar(keys %base) - 1;
+    rejects($filename, qr/entry count exceeds limit/, 'too many entries 
rejected');
+}
+my $total = 0;
+my $largest = 0;
+for my $data (values %base) {
+    $total += length($data);
+    $largest = length($data) if length($data) > $largest;
+}
+{
+    local $Spreadsheet::XLSX::MAX_MEMBER_SIZE = $largest;
+    local $Spreadsheet::XLSX::MAX_TOTAL_SIZE = $total;
+    local $Spreadsheet::XLSX::MAX_MEMBERS = scalar(keys %base);
+    ok(Spreadsheet::XLSX->new($filename), 'exact limits accepted');
+}
+{
+    local $Spreadsheet::XLSX::MAX_TOTAL_SIZE = $total - 1;
+    rejects($filename, qr/total size exceeds limit/, 'aggregate size 
rejected');
+}
+{
+    local $Spreadsheet::XLSX::MAX_MEMBER_SIZE = 1024;
+    my %members = (%base, 'unused.bin' => 'x' x 8192);
+    rejects(archive(\%members), qr/member size exceeds limit/,
+        'highly compressed unused member is included in preflight');
+}
+
+# Change only the central-directory size, leaving compressed data intact.
+sub forge_size {
+    my ($filename, $target, $size) = @_;
+    open my $fh, '<', $filename or die $!;
+    binmode $fh;
+    my $bytes = do { local $/; <$fh> };
+    close $fh;
+    my $offset = 0;
+    my $found = 0;
+    while (($offset = index($bytes, "PK\x01\x02", $offset)) >= 0) {
+        my ($name_length, $extra_length, $comment_length) =
+            unpack('vvv', substr($bytes, $offset + 28, 6));
+        if (substr($bytes, $offset + 46, $name_length) eq $target) {
+            substr($bytes, $offset + 24, 4) = pack('V', $size);
+            $found = 1;
+            last;
+        }
+        $offset += 46 + $name_length + $extra_length + $comment_length;
+    }
+    die 'Missing central-directory entry' unless $found;
+    open $fh, '>', $filename or die $!;
+    binmode $fh;
+    print {$fh} $bytes;
+    close $fh;
+}
+
+for my $name (sort keys %base) {
+    for my $delta (-1, 1) {
+        my $forged = archive(\%base);
+        forge_size($forged, $name, length($base{$name}) + $delta);
+        rejects($forged, qr/member size does not match metadata/,
+            "actual size checked for $name (delta $delta)");
+    }
+}
+{
+    local $Spreadsheet::XLSX::MAX_MEMBER_SIZE = 1024;
+    my %members = (%base, 'xl/sharedStrings.xml' => '<sst>' . ('x' x 8192) . 
'</sst>');
+    my $forged = archive(\%members);
+    forge_size($forged, 'xl/sharedStrings.xml', 100);
+    rejects($forged, qr/member size exceeds limit/, 'actual inflated bytes 
enforce limit');
+}
+
+done_testing();

++++++ _scmsync.obsinfo ++++++
mtime: 1793364268
commit: 58eadced768a0421dbb90cba9530c2860590f13c647fef25547893f355511b8e
url: https://src.opensuse.org/perl/perl-Spreadsheet-XLSX
revision: 58eadced768a0421dbb90cba9530c2860590f13c647fef25547893f355511b8e
projectscmsync: https://src.opensuse.org/perl/_ObsPrj

++++++ 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-30 13:44:28.000000000 +0100
@@ -0,0 +1 @@
+.osc

Reply via email to