Update besar apk finance
This commit is contained in:
+44
-1
@@ -5,13 +5,56 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org). This is always true of the master branch. Some earlier branches, including the branch from which you are reading this file, remain supported and security fixes are applied to them; if the security fix represents a breaking change, it may have to be applied as a minor or patch version.
|
||||
|
||||
## 2026-07-12 - 1.30.6
|
||||
|
||||
### Fixed
|
||||
|
||||
- Security patches.
|
||||
|
||||
## 2026-05-30 - 1.30.5
|
||||
|
||||
### Security Note
|
||||
|
||||
- File::prohibitWrappers and Drawing::setPath now reject phar paths with extra leading slashes (e.g. phar:///…) that escaped the prior parse_url-based filter.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Third-party security patches.
|
||||
|
||||
## 2026-04-19 - 1.30.4
|
||||
|
||||
### Fixed
|
||||
|
||||
- Security patches.
|
||||
|
||||
## 2026-04-09 - 1.30.3
|
||||
|
||||
### Fixed
|
||||
|
||||
- Security patches.
|
||||
- Option to whitelist external images. Security-related backport of [PR #4793](https://github.com/PHPOffice/PhpSpreadsheet/pull/4793)
|
||||
|
||||
## 2026-01-10 - 1.30.2
|
||||
|
||||
### Changed
|
||||
|
||||
- Evaluation of WEBSERVICE no longer requires external client, but will use oldCalculatedValue unless the request is for a domain in a user-supplied whitelist. Security-related backport of [PR #4751](https://github.com/PHPOffice/PhpSpreadsheet/pull/4751)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Settings methods setHttpClient, unsetHttpClient, getHttpClient, and getRequestFactory are no longer used. No replacement.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Changes to WEBSERVICE. Backport of [PR #4751](https://github.com/PHPOffice/PhpSpreadsheet/pull/4751)
|
||||
|
||||
## 2025-10-25 - 1.30.1
|
||||
|
||||
### Functionally Frozen
|
||||
|
||||
- Except for security changes, no further maintenance will be applied to this branch.
|
||||
You are encouraged to upgrade to a maintained branch as soon as possible.
|
||||
Maintained branches are master (preferred - version is 5.2.0 as of the date when this is being written), release390 (current version is 3.10.1), and release222 (2.4.1).
|
||||
Maintained branches are master (preferred - version is 5.4.0 as of the date when this is being written), 3.10.x (current version is 3.10.3), and 2.4.x (2.4.3).
|
||||
- Of particular note is that this branch should not run under Php 8.5+, and will *not* be updated to avoid deprecation notices introduced with Php 8.5.
|
||||
|
||||
## 2025-08-10 - 1.30.0
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-2025 PhpSpreadsheet Authors
|
||||
Copyright (c) 2019-2026 PhpSpreadsheet Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+3
-3
@@ -4,19 +4,19 @@
|
||||
[](https://coveralls.io/github/PHPOffice/PhpSpreadsheet?branch=master)
|
||||
[](https://packagist.org/packages/phpoffice/phpspreadsheet)
|
||||
[](https://packagist.org/packages/phpoffice/phpspreadsheet)
|
||||
[](https://packagist.org/packages/phpoffice/phpspreadsheet)
|
||||
[](https://packagist.org/packages/phpoffice/phpspreadsheet)
|
||||
[](https://gitter.im/PHPOffice/PhpSpreadsheet)
|
||||
|
||||
PhpSpreadsheet is a library written in pure PHP and offers a set of classes that
|
||||
allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc.
|
||||
|
||||
This branch (release1291) is *not* the latest version of PhpSpreadsheet, and may therefore lack features and bug fixes found in the latest version.
|
||||
This branch (1.30.x) is *not* the latest version of PhpSpreadsheet, and may therefore lack features and bug fixes found in the latest version.
|
||||
|
||||
## Security Changes Only
|
||||
|
||||
Except for security changes, no further maintenance will be applied to this branch.
|
||||
You are encouraged to upgrade to a maintained branch as soon as possible.
|
||||
Maintained branches are master (preferred - version is 5.2.0 as of the date when this is being written), release390 (current version is 3.10.1), and release222 (2.4.1).
|
||||
Maintained branches are master (preferred - version is 5.4.0 as of the date when this is being written), 3.10.x (current version is 3.10.3), and 2.4.x (2.4.3).
|
||||
|
||||
Of particular note is that this branch should not run under Php 8.5, and will *not* be updated to avoid deprecation notices which will be introduced with Php 8.5.
|
||||
|
||||
|
||||
+4
-2
@@ -39,6 +39,9 @@
|
||||
},
|
||||
{
|
||||
"name": "Adrien Crivelli"
|
||||
},
|
||||
{
|
||||
"name": "Owen Leibman"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
@@ -81,12 +84,11 @@
|
||||
"maennchen/zipstream-php": "^2.1 || ^3.0",
|
||||
"markbaker/complex": "^3.0",
|
||||
"markbaker/matrix": "^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||
"doctrine/instantiator": "^1.5",
|
||||
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"mitoteam/jpgraph": "^10.3",
|
||||
|
||||
+1
@@ -2789,6 +2789,7 @@ class Calculation
|
||||
'category' => Category::CATEGORY_WEB,
|
||||
'functionCall' => [Web\Service::class, 'webService'],
|
||||
'argumentCount' => '1',
|
||||
'passCellReference' => true,
|
||||
],
|
||||
'WEEKDAY' => [
|
||||
'category' => Category::CATEGORY_DATE_AND_TIME,
|
||||
|
||||
@@ -26,6 +26,8 @@ class Functions
|
||||
const RETURNDATE_PHP_DATETIME_OBJECT = 'O';
|
||||
const RETURNDATE_EXCEL = 'E';
|
||||
|
||||
public const NOT_YET_IMPLEMENTED = '#Not Yet Implemented';
|
||||
|
||||
/**
|
||||
* Compatibility mode to use for error checking and responses.
|
||||
*
|
||||
|
||||
@@ -21,7 +21,7 @@ class Web
|
||||
* Use the webService() method in the Web\Service class instead
|
||||
* @see Web\Service::webService()
|
||||
*
|
||||
* @return string the output resulting from a call to the webservice
|
||||
* @return ?string the output resulting from a call to the webservice
|
||||
*/
|
||||
public static function WEBSERVICE(string $url)
|
||||
{
|
||||
|
||||
+40
-24
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Web;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Settings;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
|
||||
class Service
|
||||
{
|
||||
@@ -16,40 +16,56 @@ class Service
|
||||
* Excel Function:
|
||||
* Webservice(url)
|
||||
*
|
||||
* @return string the output resulting from a call to the webservice
|
||||
* @param mixed $url
|
||||
*
|
||||
* @return ?string the output resulting from a call to the webservice
|
||||
*/
|
||||
public static function webService(string $url)
|
||||
public static function webService($url, ?Cell $cell = null)
|
||||
{
|
||||
$url = trim($url);
|
||||
if (strlen($url) > 2048) {
|
||||
if (is_array($url)) {
|
||||
$url = Functions::flattenSingleValue($url);
|
||||
}
|
||||
if (!is_string($url)) {
|
||||
return ExcelError::VALUE(); // Invalid URL length
|
||||
}
|
||||
|
||||
if (!preg_match('/^http[s]?:\/\//', $url)) {
|
||||
$url = trim($url);
|
||||
if (mb_strlen($url) > 2048) {
|
||||
return ExcelError::VALUE(); // Invalid URL length
|
||||
}
|
||||
$parsed = parse_url($url);
|
||||
$scheme = $parsed['scheme'] ?? '';
|
||||
if ($scheme !== 'http' && $scheme !== 'https') {
|
||||
return ExcelError::VALUE(); // Invalid protocol
|
||||
}
|
||||
|
||||
// Get results from the the webservice
|
||||
$client = Settings::getHttpClient();
|
||||
$requestFactory = Settings::getRequestFactory();
|
||||
$request = $requestFactory->createRequest('GET', $url);
|
||||
|
||||
try {
|
||||
$response = $client->sendRequest($request);
|
||||
} catch (ClientExceptionInterface $e) {
|
||||
return ExcelError::VALUE(); // cURL error
|
||||
$domainWhiteList = [];
|
||||
if ($cell !== null) {
|
||||
$parent = $cell->getWorksheet()->getParent();
|
||||
if ($parent !== null) {
|
||||
$domainWhiteList = $parent->getDomainWhiteList();
|
||||
}
|
||||
}
|
||||
$host = $parsed['host'] ?? '';
|
||||
if (!in_array($host, $domainWhiteList, true)) {
|
||||
return ($cell === null) ? null : Functions::NOT_YET_IMPLEMENTED; // will be converted to oldCalculatedValue or null
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() != 200) {
|
||||
return ExcelError::VALUE(); // cURL error
|
||||
// Get results from the webservice
|
||||
$ctxArray = [
|
||||
'http' => [
|
||||
'follow_location' => 0,
|
||||
'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
],
|
||||
];
|
||||
if ($scheme === 'https') {
|
||||
$ctxArray['ssl'] = ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT];
|
||||
}
|
||||
|
||||
$output = $response->getBody()->getContents();
|
||||
if (strlen($output) > 32767) {
|
||||
$ctx = stream_context_create($ctxArray);
|
||||
$output = @file_get_contents($url, false, $ctx);
|
||||
if ($output === false || mb_strlen($output) > 32767) {
|
||||
return ExcelError::VALUE(); // Output not a string or too long
|
||||
}
|
||||
|
||||
return $output;
|
||||
return ($output === '') ? Functions::NOT_YET_IMPLEMENTED : $output;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,17 +23,15 @@ class Downloader
|
||||
|
||||
public function __construct(string $folder, string $filename, ?string $filetype = null)
|
||||
{
|
||||
if ((is_dir($folder) === false) || (is_readable($folder) === false)) {
|
||||
throw new Exception('Folder is not accessible');
|
||||
}
|
||||
$filepath = "{$folder}/{$filename}";
|
||||
$this->filepath = (string) realpath($filepath);
|
||||
$this->filename = basename($filepath);
|
||||
if ((file_exists($this->filepath) === false) || (is_readable($this->filepath) === false)) {
|
||||
clearstatcache();
|
||||
$filepath = realpath("{$folder}/{$filename}");
|
||||
if ($filepath === false || !is_file($filepath) || !is_readable($filepath)) {
|
||||
throw new Exception('File not found, or cannot be read');
|
||||
}
|
||||
$this->filepath = $filepath;
|
||||
$this->filename = basename($this->filepath);
|
||||
|
||||
$filetype ??= pathinfo($filename, PATHINFO_EXTENSION);
|
||||
$filetype ??= pathinfo($this->filename, PATHINFO_EXTENSION);
|
||||
if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) {
|
||||
throw new Exception('Invalid filetype: cannot be downloaded');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ abstract class IOFactory
|
||||
public const WRITER_CSV = 'Csv';
|
||||
public const WRITER_HTML = 'Html';
|
||||
|
||||
/** @var string[] */
|
||||
/** @var array<string, class-string<IReader>> */
|
||||
private static $readers = [
|
||||
self::READER_XLSX => Reader\Xlsx::class,
|
||||
self::READER_XLS => Reader\Xls::class,
|
||||
@@ -236,4 +236,16 @@ abstract class IOFactory
|
||||
|
||||
self::$readers[$readerType] = $readerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, class-string<IReader>>
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function getReaders(): array
|
||||
{
|
||||
return self::$readers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
|
||||
use Closure;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||
@@ -68,6 +69,9 @@ abstract class BaseReader implements IReader
|
||||
*/
|
||||
protected $securityScanner;
|
||||
|
||||
/** @var null|Closure(string):bool function to return whether image path is okay */
|
||||
protected ?Closure $isWhitelisted = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->readFilter = new DefaultReadFilter();
|
||||
@@ -220,9 +224,10 @@ abstract class BaseReader implements IReader
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow external images. Use with caution.
|
||||
* Improper specification of these within a spreadsheet
|
||||
* can subject the caller to security exploits.
|
||||
* USE WITH CAUTION (and in conjunction with setIsWhiteListed)!
|
||||
* Allow external images;
|
||||
* these can be specified within a spreadsheet
|
||||
* in a way that can subject the caller to security exploits.
|
||||
*/
|
||||
public function setAllowExternalImages(bool $allowExternalImages)
|
||||
{
|
||||
@@ -235,4 +240,20 @@ abstract class BaseReader implements IReader
|
||||
{
|
||||
return $this->allowExternalImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* USE WITH CAUTION!
|
||||
* Supply a callback to determine whether a path should be whitelisted,
|
||||
* used in conjunction with setAllowExternalImages;
|
||||
* supplying a method which might return true
|
||||
* can subject the caller to security exploits.
|
||||
*
|
||||
* @param Closure(string):bool $isWhitelisted
|
||||
*/
|
||||
public function setIsWhitelisted(Closure $isWhitelisted): self
|
||||
{
|
||||
$this->isWhitelisted = $isWhitelisted;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,14 @@ class Gnumeric extends BaseReader
|
||||
],
|
||||
];
|
||||
|
||||
protected int $maxLength;
|
||||
|
||||
private const LENGTH_MULTIPLIER = [
|
||||
'G' => 1024 * 1024 * 1024,
|
||||
'M' => 1024 * 1024,
|
||||
'K' => 1024,
|
||||
];
|
||||
|
||||
/**
|
||||
* Create a new Gnumeric.
|
||||
*/
|
||||
@@ -72,6 +80,20 @@ class Gnumeric extends BaseReader
|
||||
parent::__construct();
|
||||
$this->referenceHelper = ReferenceHelper::getInstance();
|
||||
$this->securityScanner = XmlScanner::getInstance($this);
|
||||
$limit = ini_get('memory_limit') ?: '128M';
|
||||
$limit = trim(str_replace('-1', '128M', $limit));
|
||||
$unit = strtoupper(substr($limit, -1));
|
||||
$limit = (int) $limit;
|
||||
$multiplier = self::LENGTH_MULTIPLIER[$unit] ?? 1;
|
||||
$limit *= $multiplier;
|
||||
$this->maxLength = intdiv($limit, 4);
|
||||
}
|
||||
|
||||
public function setMaxLength(int $maxLength): self
|
||||
{
|
||||
$this->maxLength = $maxLength;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,7 +218,7 @@ class Gnumeric extends BaseReader
|
||||
if (substr($contents, 0, 2) === "\x1f\x8b") {
|
||||
// Check if gzlib functions are available
|
||||
if (function_exists('gzdecode')) {
|
||||
$contents = @gzdecode($contents);
|
||||
$contents = @gzdecode($contents, $this->maxLength);
|
||||
if ($contents !== false) {
|
||||
$data = $contents;
|
||||
}
|
||||
|
||||
@@ -1084,7 +1084,7 @@ class Html extends BaseReader
|
||||
$name = $attributes['alt'] ?? null;
|
||||
|
||||
$drawing = new Drawing();
|
||||
$drawing->setPath($src, false, null, $this->allowExternalImages);
|
||||
$drawing->setPath($src, false, null, $this->allowExternalImages, $this->isWhitelisted);
|
||||
if ($drawing->getPath() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1436,7 +1436,7 @@ class Xlsx extends BaseReader
|
||||
);
|
||||
if (isset($images[$linkImageKey])) {
|
||||
$url = str_replace('xl/drawings/', '', $images[$linkImageKey]);
|
||||
$objDrawing->setPath($url, false, null, $this->allowExternalImages);
|
||||
$objDrawing->setPath($url, false, null, $this->allowExternalImages, $this->isWhitelisted);
|
||||
}
|
||||
if ($objDrawing->getPath() === '') {
|
||||
continue;
|
||||
@@ -1525,7 +1525,7 @@ class Xlsx extends BaseReader
|
||||
);
|
||||
if (isset($images[$linkImageKey])) {
|
||||
$url = str_replace('xl/drawings/', '', $images[$linkImageKey]);
|
||||
$objDrawing->setPath($url, false, null, $this->allowExternalImages);
|
||||
$objDrawing->setPath($url, false, null, $this->allowExternalImages, $this->isWhitelisted);
|
||||
}
|
||||
if ($objDrawing->getPath() === '') {
|
||||
continue;
|
||||
|
||||
+14
-6
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
|
||||
@@ -196,24 +197,31 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
{
|
||||
$rowAttributes = [];
|
||||
|
||||
$rowIndex = 0;
|
||||
foreach ($worksheetRow as $rowx) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$row = $rowx->attributes();
|
||||
++$rowIndex;
|
||||
if ($row !== null) {
|
||||
if (isset($row['r'])) {
|
||||
$rowIndex = (int) $row['r'];
|
||||
}
|
||||
if ($rowIndex < 1 || $rowIndex > AddressRange::MAX_ROW) {
|
||||
continue;
|
||||
}
|
||||
if (isset($row['ht']) && !$readDataOnly) {
|
||||
$rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
|
||||
$rowAttributes[$rowIndex]['rowHeight'] = (float) $row['ht'];
|
||||
}
|
||||
if (isset($row['hidden']) && self::boolean($row['hidden'])) {
|
||||
$rowAttributes[(int) $row['r']]['visible'] = false;
|
||||
$rowAttributes[$rowIndex]['visible'] = false;
|
||||
}
|
||||
if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
|
||||
$rowAttributes[(int) $row['r']]['collapsed'] = true;
|
||||
$rowAttributes[$rowIndex]['collapsed'] = true;
|
||||
}
|
||||
if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
|
||||
$rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
|
||||
$rowAttributes[$rowIndex]['outlineLevel'] = (int) $row['outlineLevel'];
|
||||
}
|
||||
if (isset($row['s']) && !$readDataOnly) {
|
||||
$rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
|
||||
$rowAttributes[$rowIndex]['xfIndex'] = (int) $row['s'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\DefinedName;
|
||||
@@ -75,6 +76,7 @@ class Xml extends BaseReader
|
||||
];
|
||||
|
||||
// Open file
|
||||
File::assertFile($filename);
|
||||
$data = (string) file_get_contents($filename);
|
||||
$data = $this->getSecurityScannerOrThrow()->scan($data);
|
||||
|
||||
@@ -353,15 +355,19 @@ class Xml extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
$rowID = 1;
|
||||
$rowID = 0;
|
||||
if (isset($worksheet->Table->Row)) {
|
||||
$additionalMergedCells = 0;
|
||||
foreach ($worksheet->Table->Row as $rowData) {
|
||||
$rowHasData = false;
|
||||
++$rowID;
|
||||
$row_ss = self::getAttributes($rowData, self::NAMESPACES_SS);
|
||||
if (isset($row_ss['Index'])) {
|
||||
$rowID = (int) $row_ss['Index'];
|
||||
}
|
||||
if ($rowID < 1 || $rowID > AddressRange::MAX_ROW) {
|
||||
continue;
|
||||
}
|
||||
if (isset($row_ss['Hidden'])) {
|
||||
$rowVisible = ((string) $row_ss['Hidden']) !== '1';
|
||||
$spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible);
|
||||
@@ -495,8 +501,6 @@ class Xml extends BaseReader
|
||||
$spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight);
|
||||
}
|
||||
}
|
||||
|
||||
++$rowID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-15
@@ -5,8 +5,6 @@ namespace PhpOffice\PhpSpreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer;
|
||||
use PhpOffice\PhpSpreadsheet\Collection\Memory;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use ReflectionClass;
|
||||
|
||||
@@ -37,12 +35,12 @@ class Settings
|
||||
/**
|
||||
* The HTTP client implementation to be used for network request.
|
||||
*
|
||||
* @var null|ClientInterface
|
||||
* @var mixed
|
||||
*/
|
||||
private static $httpClient;
|
||||
|
||||
/**
|
||||
* @var null|RequestFactoryInterface
|
||||
* @var mixed
|
||||
*/
|
||||
private static $requestFactory;
|
||||
|
||||
@@ -181,8 +179,13 @@ class Settings
|
||||
|
||||
/**
|
||||
* Set the HTTP client implementation to be used for network request.
|
||||
*
|
||||
* @param mixed $httpClient
|
||||
* @param mixed $requestFactory
|
||||
*
|
||||
* @deprecated 1.30.2 No replacement.
|
||||
*/
|
||||
public static function setHttpClient(ClientInterface $httpClient, RequestFactoryInterface $requestFactory): void
|
||||
public static function setHttpClient($httpClient, $requestFactory): void
|
||||
{
|
||||
self::$httpClient = $httpClient;
|
||||
self::$requestFactory = $requestFactory;
|
||||
@@ -190,6 +193,8 @@ class Settings
|
||||
|
||||
/**
|
||||
* Unset the HTTP client configuration.
|
||||
*
|
||||
* @deprecated 1.30.2 No replacement.
|
||||
*/
|
||||
public static function unsetHttpClient(): void
|
||||
{
|
||||
@@ -199,25 +204,25 @@ class Settings
|
||||
|
||||
/**
|
||||
* Get the HTTP client implementation to be used for network request.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated 1.30.2 No replacement.
|
||||
*/
|
||||
public static function getHttpClient(): ClientInterface
|
||||
public static function getHttpClient()
|
||||
{
|
||||
if (!self::$httpClient || !self::$requestFactory) {
|
||||
throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.');
|
||||
}
|
||||
|
||||
return self::$httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTTP request factory.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated 1.30.2 No replacement.
|
||||
*/
|
||||
public static function getRequestFactory(): RequestFactoryInterface
|
||||
public static function getRequestFactory()
|
||||
{
|
||||
if (!self::$httpClient || !self::$requestFactory) {
|
||||
throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.');
|
||||
}
|
||||
|
||||
return self::$requestFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Shared;
|
||||
|
||||
use Composer\Pcre\Preg;
|
||||
use PhpOffice\PhpSpreadsheet\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
||||
use ZipArchive;
|
||||
@@ -140,11 +141,33 @@ class File
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks phar:// and similar RCE-bearing wrappers.
|
||||
* Note that many protocols, including http and zip, will already
|
||||
* return false for is_file.
|
||||
* A whitelist of protocols may be added if needed in future.
|
||||
* data: is intentionally allowed; callers needing strict
|
||||
* on-disk-only semantics must validate $filename themselves.
|
||||
*/
|
||||
public static function prohibitWrappers(string $filename): void
|
||||
{
|
||||
if (
|
||||
Preg::IsMatch('~^phar://~i', $filename)
|
||||
|| (Preg::isMatch('/^([\w.\s\x00-\x1f]+):/', $filename) && !Preg::isMatch('/^([\w.]+):/', $filename))
|
||||
|| Preg::isMatch('~^[\w.]+://.*phar:~is', $filename)
|
||||
) {
|
||||
throw new Exception(
|
||||
"Disallowed stream wrapper: {$filename}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that given path is an existing file and is readable, otherwise throw exception.
|
||||
*/
|
||||
public static function assertFile(string $filename, string $zipMember = ''): void
|
||||
{
|
||||
self::prohibitWrappers($filename);
|
||||
if (!is_file($filename)) {
|
||||
throw new ReaderException('File "' . $filename . '" does not exist.');
|
||||
}
|
||||
@@ -167,9 +190,11 @@ class File
|
||||
|
||||
/**
|
||||
* Same as assertFile, except return true/false and don't throw Exception.
|
||||
* Will nevertheless throw if filename uses invalid protocol, e.g. phar.
|
||||
*/
|
||||
public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool
|
||||
{
|
||||
self::prohibitWrappers($filename);
|
||||
if (!is_file($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,9 @@ class OLERead
|
||||
*/
|
||||
private $props = [];
|
||||
|
||||
/** @var int[] */
|
||||
private array $possibleLoop = [];
|
||||
|
||||
/**
|
||||
* Read the file.
|
||||
*/
|
||||
@@ -170,7 +173,9 @@ class OLERead
|
||||
|
||||
$sbdBlock = $this->sbdStartBlock;
|
||||
$this->smallBlockChain = '';
|
||||
$this->possibleLoop = [];
|
||||
while ($sbdBlock != -2) {
|
||||
$this->catchLoop($sbdBlock);
|
||||
$pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
|
||||
|
||||
$this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
|
||||
@@ -186,6 +191,14 @@ class OLERead
|
||||
$this->readPropertySets();
|
||||
}
|
||||
|
||||
private function catchLoop(int $sbdBlock): void
|
||||
{
|
||||
if (in_array($sbdBlock, $this->possibleLoop, true)) {
|
||||
throw new ReaderException('Detected loop while iterating blocks');
|
||||
}
|
||||
$this->possibleLoop[] = $sbdBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract binary stream data.
|
||||
*
|
||||
@@ -206,7 +219,9 @@ class OLERead
|
||||
|
||||
$block = $this->props[$stream]['startBlock'];
|
||||
|
||||
$this->possibleLoop = [];
|
||||
while ($block != -2) {
|
||||
$this->catchLoop($block);
|
||||
$pos = $block * self::SMALL_BLOCK_SIZE;
|
||||
$streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
|
||||
|
||||
@@ -226,7 +241,9 @@ class OLERead
|
||||
|
||||
$block = $this->props[$stream]['startBlock'];
|
||||
|
||||
$this->possibleLoop = [];
|
||||
while ($block != -2) {
|
||||
$this->catchLoop($block);
|
||||
$pos = ($block + 1) * self::BIG_BLOCK_SIZE;
|
||||
$streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
|
||||
$block = self::getInt4d($this->bigBlockChain, $block * 4);
|
||||
@@ -246,7 +263,9 @@ class OLERead
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$this->possibleLoop = [];
|
||||
while ($block != -2) {
|
||||
$this->catchLoop($block);
|
||||
$pos = ($block + 1) * self::BIG_BLOCK_SIZE;
|
||||
$data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
|
||||
$block = self::getInt4d($this->bigBlockChain, $block * 4);
|
||||
|
||||
@@ -1679,4 +1679,25 @@ class Spreadsheet implements JsonSerializable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var string[] */
|
||||
private $domainWhiteList = [];
|
||||
|
||||
/**
|
||||
* Currently used only by WEBSERVICE function.
|
||||
*
|
||||
* @param string[] $domainWhiteList
|
||||
*/
|
||||
public function setDomainWhiteList(array $domainWhiteList): self
|
||||
{
|
||||
$this->domainWhiteList = $domainWhiteList;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getDomainWhiteList(): array
|
||||
{
|
||||
return $this->domainWhiteList;
|
||||
}
|
||||
}
|
||||
|
||||
+28
-2
@@ -14,6 +14,7 @@ class Formatter
|
||||
* Matches any @ symbol that isn't enclosed in quotes.
|
||||
*/
|
||||
private const SYMBOL_AT = '/@(?=(?:[^"]*"[^"]*")*[^"]*\Z)/miu';
|
||||
private const QUOTE_REPLACEMENT = "\u{fffe}"; // invalid Unicode character
|
||||
|
||||
/**
|
||||
* Matches any ; symbol that isn't enclosed in quotes, for a "section" split.
|
||||
@@ -137,8 +138,33 @@ class Formatter
|
||||
}
|
||||
// For now we do not treat strings in sections, although section 4 of a format code affects strings
|
||||
// Process a single block format code containing @ for text substitution
|
||||
if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $format) === 1) {
|
||||
return str_replace('"', '', preg_replace(self::SYMBOL_AT, (string) $value, $format) ?? '');
|
||||
$formatx = str_replace('\"', self::QUOTE_REPLACEMENT, $format);
|
||||
if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $formatx) === 1) {
|
||||
if (strpos($format, '"') === false) {
|
||||
$temp = str_replace('@', "$value", $format);
|
||||
if (is_callable($callBack)) {
|
||||
$temp = $callBack($temp, $format);
|
||||
}
|
||||
|
||||
return $temp;
|
||||
}
|
||||
//escape any dollar signs on the string, so they are not replaced with an empty value
|
||||
$value = str_replace(
|
||||
['$', '"'],
|
||||
['\$', self::QUOTE_REPLACEMENT],
|
||||
(string) $value
|
||||
);
|
||||
$temp = preg_replace(self::SYMBOL_AT, $value, $formatx) ?? $value;
|
||||
if (is_callable($callBack)) {
|
||||
$temp = $callBack($temp, $formatx);
|
||||
}
|
||||
/** @var string $temp */
|
||||
|
||||
return str_replace(
|
||||
['"', self::QUOTE_REPLACEMENT],
|
||||
['', '"'],
|
||||
$temp
|
||||
);
|
||||
}
|
||||
|
||||
// If we have a text value, return it "as is"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Worksheet;
|
||||
|
||||
use Composer\Pcre\Preg;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use ZipArchive;
|
||||
|
||||
@@ -102,33 +103,50 @@ class Drawing extends BaseDrawing
|
||||
* @param bool $verifyFile Verify file
|
||||
* @param ZipArchive $zip Zip archive instance
|
||||
* @param bool $allowExternal
|
||||
* @param null|callable(string):bool $isWhitelisted
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPath($path, $verifyFile = true, $zip = null, $allowExternal = true)
|
||||
public function setPath($path, $verifyFile = true, $zip = null, $allowExternal = true, ?callable $isWhitelisted = null)
|
||||
{
|
||||
$this->isUrl = false;
|
||||
if (preg_match('~^data:image/[a-z]+;base64,~', $path) === 1) {
|
||||
if (Preg::isMatch('~^data:image/[a-z]+;base64,~', $path)) {
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->path = '';
|
||||
if ($zip instanceof ZipArchive) {
|
||||
$zipPath = explode('#', $path)[1];
|
||||
$locate = @$zip->locateName($zipPath);
|
||||
if ($locate !== false) {
|
||||
if ($this->isImage($path)) {
|
||||
$this->path = $path;
|
||||
$this->setSizesAndType($path);
|
||||
}
|
||||
}
|
||||
// Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979
|
||||
if (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\w\s\x00-\x1f]+):/u', $path) && !preg_match('/^([\w]+):/u', $path))) {
|
||||
if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) {
|
||||
} elseif (
|
||||
filter_var($path, FILTER_VALIDATE_URL)
|
||||
|| Preg::isMatch('~^phar://~i', $path)
|
||||
|| (Preg::isMatch('/^([\w.\s\x00-\x1f]+):/', $path) && !Preg::isMatch('/^([\w.]+):/', $path))
|
||||
) {
|
||||
if (!Preg::isMatch('/^(http|https|file|ftp|s3):/', $path)) {
|
||||
throw new PhpSpreadsheetException('Invalid protocol for linked drawing');
|
||||
}
|
||||
if (!$allowExternal) {
|
||||
return $this;
|
||||
}
|
||||
if ($isWhitelisted !== null && !$isWhitelisted($path)) {
|
||||
return $this;
|
||||
}
|
||||
// Implicit that it is a URL, rather store info than running check above on value in other places.
|
||||
$this->isUrl = true;
|
||||
$ctx = null;
|
||||
// https://github.com/php/php-src/issues/16023
|
||||
// https://github.com/php/php-src/issues/17121
|
||||
if (preg_match('/^https?:/', $path) === 1) {
|
||||
if (Preg::isMatch('/^https?:/', $path)) {
|
||||
$ctxArray = [
|
||||
'http' => [
|
||||
'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
@@ -138,7 +156,7 @@ class Drawing extends BaseDrawing
|
||||
],
|
||||
],
|
||||
];
|
||||
if (preg_match('/^https:/', $path) === 1) {
|
||||
if (Preg::isMatch('/^https:/', $path)) {
|
||||
$ctxArray['ssl'] = ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT];
|
||||
}
|
||||
$ctx = stream_context_create($ctxArray);
|
||||
@@ -157,15 +175,6 @@ class Drawing extends BaseDrawing
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($zip instanceof ZipArchive) {
|
||||
$zipPath = explode('#', $path)[1];
|
||||
$locate = @$zip->locateName($zipPath);
|
||||
if ($locate !== false) {
|
||||
if ($this->isImage($path)) {
|
||||
$this->path = $path;
|
||||
$this->setSizesAndType($path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$exists = @file_exists($path);
|
||||
if ($exists !== false && $this->isImage($path)) {
|
||||
|
||||
@@ -1724,7 +1724,7 @@ class Html extends BaseWriter
|
||||
}
|
||||
|
||||
// convert to PCDATA
|
||||
$result = htmlspecialchars($value, Settings::htmlEntityFlags());
|
||||
$result = htmlspecialchars($value, ENT_NOQUOTES);
|
||||
|
||||
// color span tag
|
||||
if ($color !== null) {
|
||||
|
||||
Reference in New Issue
Block a user