Update besar apk finance

This commit is contained in:
Wian Drs
2026-09-11 16:03:00 +07:00
parent d42483b350
commit 4ef15c22ae
897 changed files with 98056 additions and 15979 deletions
+44 -1
View File
@@ -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
View File
@@ -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
View File
@@ -4,19 +4,19 @@
[![Code Coverage](https://coveralls.io/repos/github/PHPOffice/PhpSpreadsheet/badge.svg?branch=master)](https://coveralls.io/github/PHPOffice/PhpSpreadsheet?branch=master)
[![Total Downloads](https://img.shields.io/packagist/dt/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet)
[![Latest Stable Version](https://img.shields.io/github/v/release/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet)
[![License](https://img.shields.io/github/license/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet)
[![License](https://poser.pugx.org/phpoffice/phpspreadsheet/license)](https://packagist.org/packages/phpoffice/phpspreadsheet)
[![Join the chat at https://gitter.im/PHPOffice/PhpSpreadsheet](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](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
View File
@@ -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",
@@ -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)
{
@@ -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;
@@ -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;
}
}
@@ -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;
}
}
@@ -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) {