Compare commits

...

8 Commits

Author SHA1 Message Date
Benjamin DALSASS
553103365e N°9236 - tag admin fix 2026-03-06 14:33:13 +01:00
lenaick.moreira
307c308eb0 Fix unit test testPhpMinVersionConsistency 2026-03-05 16:15:18 +01:00
Lenaick
61e5536b50 N°9234 - Sanitize query expression parameter in suggested OQL on run query page (#829) 2026-03-05 16:02:30 +01:00
Lenaick
104dd1970f N°9230 - Sanitize dashboard_id parameter in "revert_dashboard" operation of AJAX render function (#828) 2026-03-05 15:55:28 +01:00
Stephen Abello
929b8b9eca Fix CI by fixing code style 2026-03-05 15:28:27 +01:00
Stephen Abello
3b8e079cf1 N°6977 - Sanitize Excel formulas in exports (#818)
* N°6977 - Sanitize Excel formulas in export in the backoffice
---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2026-03-05 15:07:27 +01:00
Lenaick
fc967c06ce N°8834 - Add compatibility with PHP 8.4 (#819)
* N°8834 - Add compatibility with PHP 8.4

* Rollback of scssphp/scssphp version upgrade due to compilation error
2026-02-26 10:36:32 +01:00
Eric Espie
d4821b7edc remove log level config 2026-02-26 10:19:34 +01:00
994 changed files with 12953 additions and 7464 deletions

View File

@@ -979,7 +979,9 @@ CSS;
}
}
}
array_map(function ($sVariableValue) { return ltrim($sVariableValue); }, $aVariablesResults);
array_map(function ($sVariableValue) {
return ltrim($sVariableValue);
}, $aVariablesResults);
return $aVariablesResults;
}

View File

@@ -2081,7 +2081,9 @@ SQL;
}
// Remove any remaining nulls (for positions that weren't referenced)
$aReplacements = array_filter($aReplacements, static function ($val) { return $val !== null; });
$aReplacements = array_filter($aReplacements, static function ($val) {
return $val !== null;
});
} else {
// For non-positional, we need to map each position
$aReplacements = [];

View File

@@ -4,7 +4,7 @@
"type": "project",
"license": "AGPL-3.0-only",
"require": {
"php": ">=8.1.0 <8.4.0",
"php": ">=8.1.0 <8.5.0",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -12,8 +12,7 @@
"ext-json": "*",
"ext-mysqli": "*",
"ext-soap": "*",
"apereo/phpcas": "~1.6.0",
"firebase/php-jwt": "^6.4.0",
"apereo/phpcas": "dev-master",
"guzzlehttp/guzzle": "^7.5.1",
"league/oauth2-google": "^4.0.1",
"nikic/php-parser": "^4.14.0",
@@ -40,6 +39,12 @@
"symfony/stopwatch": "~6.4.0",
"symfony/web-profiler-bundle": "~6.4.0"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/EsupPortail/phpCAS"
}
],
"suggest": {
"ext-libsodium": "Required to use the AttributeEncryptedString.",
"ext-openssl": "Can be used as a polyfill if libsodium is not installed",

852
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\ExportHelper;
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
@@ -13,7 +14,6 @@ use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectUIBlockFactory
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
use Combodo\iTop\Application\Helper\ExportHelper;
use Combodo\iTop\Application\WebPage\Page;
use Combodo\iTop\Application\WebPage\WebPage;
@@ -55,6 +55,8 @@ class CSVBulkExport extends TabularBulkExport
$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data'));
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
$this->aStatusInfo['ignore_excel_sanitization'] = (bool)utils::ReadParam('ignore_excel_sanitization', 0, true, utils::ENUM_SANITIZATION_FILTER_INTEGER);
$sDateFormatRadio = utils::ReadParam('csv_date_format_radio', '');
switch ($sDateFormatRadio) {
case 'default':
@@ -223,6 +225,10 @@ class CSVBulkExport extends TabularBulkExport
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
$oFieldSetDate->AddSubBlock($oRadioCustom);
$oFieldSetSecurity = FieldSetUIBlockFactory::MakeStandard(Dict::S('Core:BulkExport:Security'));
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oFieldSetSecurity));
$oFieldSetSecurity->AddSubBlock(ExportHelper::GetInputForSanitizeExcelExport());
$oP->add_ready_script(
<<<EOF
$('#form_part_csv_options').on('preview_updated', function() { FormatDatesInPreview('csv', 'csv'); });
@@ -264,6 +270,13 @@ EOF
default:
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
}
// If the option to ignore Excel sanitization is not set or explicitly set to false, apply sanitization
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
return ExportHelper::SanitizeField($sRet, $this->aStatusInfo['text_qualifier'] ?? '');
}
// The option to ignore Excel sanitization is explicitly set to true: return the raw value without sanitization
return $sRet;
}
@@ -337,6 +350,12 @@ EOF
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']);
}
}
// If the option to ignore Excel sanitization is not set or absent, sanitize the field
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
$sField = ExportHelper::SanitizeField($sField, $this->aStatusInfo['text_qualifier']);
}
if ($this->aStatusInfo['charset'] != 'UTF-8') {
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
// and thus to convert field by field and not the whole row or file at once (see ticket N°991)

View File

@@ -5,13 +5,13 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\ExportHelper;
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
use Combodo\iTop\Application\Helper\ExportHelper;
use Combodo\iTop\Application\WebPage\Page;
use Combodo\iTop\Application\WebPage\WebPage;
@@ -63,6 +63,8 @@ class ExcelBulkExport extends TabularBulkExport
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
}
$this->aStatusInfo['ignore_excel_sanitization'] = (bool)utils::ReadParam('ignore_excel_sanitization', 0, true, utils::ENUM_SANITIZATION_FILTER_INTEGER);
}
public function EnumFormParts()
@@ -121,6 +123,10 @@ class ExcelBulkExport extends TabularBulkExport
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
$oFieldSetDate->AddSubBlock($oRadioCustom);
$oFieldSetSecurity = FieldSetUIBlockFactory::MakeStandard(Dict::S('Core:BulkExport:Security'));
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oFieldSetSecurity));
$oFieldSetSecurity->AddSubBlock(ExportHelper::GetInputForSanitizeExcelExport());
$oP->add_ready_script(
<<<EOF
$('#form_part_xlsx_options').on('preview_updated', function() { FormatDatesInPreview('excel', 'xlsx'); });
@@ -216,6 +222,12 @@ EOF
}
}
}
// If the option to ignore Excel sanitization is not set or absent, sanitize the field
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
return ExportHelper::SanitizeField($sRet, '');
}
return $sRet;
}

View File

@@ -4,3 +4,4 @@
*/
@import "bulk-modify";
@import "bulk-export";

View File

@@ -0,0 +1,10 @@
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
#form_part_csv_options:has(#ibo-sanitize-excel-export--input:checked), #form_part_xlsx_options:has(#ibo-sanitize-excel-export--input:checked){
#ibo-sanitize-excel-export--alert {
display: none;
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
@@ -8,5 +9,4 @@ namespace Combodo\iTop\DBTools\Exception;
class AuthenticationException extends \Exception
{
}
}

View File

@@ -11,7 +11,7 @@
</div>
<div id="export-feedback">
<p id="export-excel-warning" class="alert alert-warning" role="alert">{{ 'UI:Bulk:Export:MaliciousInjection:Alert:Message'|dict_format(sWikiUrl)|raw }}</p>
<p id="export-excel-warning" class="alert alert-warning" role="alert">{{ 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message'|dict_format(sWikiUrl)|raw }}</p>
<p class="export-message" style="text-align:center;">{{ 'ExcelExport:PreparingExport'|dict_s }}</p>
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%"

View File

@@ -13,5 +13,9 @@
Dict::Add('CS CZ', 'Czech', 'Čeština', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('DA DA', 'Danish', 'Dansk', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('DE DE', 'German', 'Deutsch', [
'UI:Bulk:modify:IncompatibleAttribute' => 'Dieses Attribut kann in einer Massenänderung nicht bearbeitet werden.',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel-Sicherheitswarnung',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Das Öffnen einer Datei mit nicht vertrauenswürdigen Daten in Microsoft Excel kann zu einer Formel-Injektion führen. Stellen Sie sicher, dass Ihre Excel-Einstellungen so konfiguriert sind, dass Dateien sicher verarbeitet werden. <a href="%1$s">Erfahren Sie mehr in unserer Dokumentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Das Öffnen einer Datei mit nicht vertrauenswürdigen Daten in Microsoft Excel kann zu einer Formel-Injektion führen. Stellen Sie sicher, dass Ihre Excel-Einstellungen so konfiguriert sind, dass Dateien sicher verarbeitet werden. <a href="%1$s" target="_blank">Erfahren Sie mehr in unserer Dokumentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -23,5 +23,9 @@ Dict::Add('EN US', 'English', 'English', [
// Bulk modify
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.',
'Core:BulkExport:Security' => 'Security',
]);

View File

@@ -10,5 +10,9 @@ Dict::Add('EN GB', 'British English', 'British English', [
// Bulk modify
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.',
'Core:BulkExport:Security' => 'Security',
]);

View File

@@ -11,5 +11,9 @@
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'UI:Bulk:modify:IncompatibleAttribute' => 'Este atributo no se puede editar en contexto masivo',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Advertencia de seguridad de Excel',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Abrir un archivo con datos que no son de confianza en Microsoft Excel puede provocar la inyección de fórmulas. Asegúrese de que la configuración de Excel esté configurada para manejar archivos de forma segura. <a href="%1$s">Obtenga más información en nuestra documentación.</a>',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Abrir un archivo con datos que no son de confianza en Microsoft Excel puede provocar la inyección de fórmulas. Asegúrese de que la configuración de Excel esté configurada para manejar archivos de forma segura. <a href="%1$s" target="_blank">Obtenga más información en nuestra documentación.</a>',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('FR FR', 'French', 'Français', [
'UI:Bulk:modify:IncompatibleAttribute' => 'Cet attribut ne peut être édité dans une modification en masse',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Avertissement sur la sécurité d\'Excel',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'L\'ouverture d\'un fichier contenant des données non fiables dans Microsoft Excel peut entraîner l\'injection de formules. Assurez-vous que vos paramètres Excel sont configurés pour traiter les fichiers en toute sécurité. <a href="%1$s">Pour en savoir plus, consultez notre documentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'L\'ouverture d\'un fichier contenant des données non fiables dans Microsoft Excel peut entraîner l\'injection de formules. Assurez-vous que vos paramètres Excel sont configurés pour traiter les fichiers en toute sécurité. <a href="%1$s" target="_blank">Pour en savoir plus, consultez notre documentation.</a>',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Certaines valeurs ont été échappées pour prévenir de potentielles failles de sécurités dans Microsoft Excel. <a href="%1$s" target="_blank">Pour en savoir plus, consultez notre documentation</a>',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitiser les valeurs potentiellement dangereuses',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'Lorsqu\'elle est activée, les valeurs potentiellement dangereuses seront sanitizées lors de l\'exportation. Cela empêchera Microsoft Excel de les interpréter comme des formules. Notez que cela peut altérer les données originales en les préfixant avec une simple quote (\') pour s\'assurer qu\'elles soient traitées comme du texte.',
'Core:BulkExport:Security' => 'Sécurité',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('IT IT', 'Italian', 'Italiano', [
'UI:Bulk:modify:IncompatibleAttribute' => 'Questo attributo non può essere modificato nel contesto di modifica bulk',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Avviso di sicurezza di Excel',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'L\'apertura di un file con dati non fidati in Microsoft Excel potrebbe comportare l\'iniezione di formule. Assicurati che le impostazioni di Excel siano configurate per gestire i file in modo sicuro. <a href="%1$s">Ulteriori informazioni nella nostra documentazione.</a>',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'L\'apertura di un file con dati non fidati in Microsoft Excel potrebbe comportare l\'iniezione di formule. Assicurati che le impostazioni di Excel siano configurate per gestire i file in modo sicuro. <a href="%1$s" target="_blank">Ulteriori informazioni nella nostra documentazione.</a>',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('JA JP', 'Japanese', '日本語', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('PL PL', 'Polish', 'Polski', [
'UI:Bulk:modify:IncompatibleAttribute' => 'Tego atrybutu nie można edytować zbiorczo',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Ostrzeżenie dotyczące bezpieczeństwa programu Excel',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Otwarcie pliku z niezaufanymi danymi w programie Microsoft Excel może spowodować wstrzyknięcie formuły. Upewnij się, że ustawienia programu Excel są skonfigurowane tak, aby bezpiecznie obsługiwać pliki. <a href="%1$s">Dowiedz się więcej w naszej dokumentacji.</a>',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Otwarcie pliku z niezaufanymi danymi w programie Microsoft Excel może spowodować wstrzyknięcie formuły. Upewnij się, że ustawienia programu Excel są skonfigurowane tak, aby bezpiecznie obsługiwać pliki. <a href="%1$s" target="_blank">Dowiedz się więcej w naszej dokumentacji.</a>',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('RU RU', 'Russian', 'Русский', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -13,5 +13,9 @@
Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel security warning~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => 'Opening a file with untrusted data in Microsoft Excel may lead to formula injection. Ensure that your Excel settings are configured to handle files safely. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -22,5 +22,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
// Bulk modify
'UI:Bulk:modify:IncompatibleAttribute' => '此属性无法在批量操作中编辑',
'UI:Bulk:Export:MaliciousInjection:Alert:Title' => 'Excel 安全警告',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => '在 Microsoft Excel 中打开不信任的文件可能导致公式注入. 请确保 Excel 设置能够安全的处理该文件. <a href="%1$s">进入我们的文档了解更多.</a>',
'UI:Bulk:Export:MaliciousInjection:Alert:Message' => '在 Microsoft Excel 中打开不信任的文件可能导致公式注入. 请确保 Excel 设置能够安全的处理该文件. <a href="%1$s" target="_blank">进入我们的文档了解更多.</a>',
'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. <a href="%1$s" target="_blank">Learn more in our documentation.</a>~~',
'UI:Bulk:Export:MaliciousInjection:Input:Label' => 'Sanitize potentially dangerous values~~',
'UI:Bulk:Export:MaliciousInjection:Input:Tooltip' => 'When enabled, potentially dangerous values will be sanitized during export. This will prevent Microsoft Excel from interpreting them as formulas. Note that this may alter the original data by prefixing it with a single quote (\') to ensure it is treated as text.~~',
'Core:BulkExport:Security' => 'Security~~',
]);

View File

@@ -487,6 +487,12 @@ function ExportStartExport() {
oParams.expression = $('#export-form :input[name=expression]').val();
oParams.query = $('#export-form :input[name=query]').val();
}
// Read the "sanitize_excel_export" checkbox if it exists, and set the corresponding "ignore_excel_sanitization" parameter
if($(':input[name=sanitize_excel_export]').length > 0) {
oParams.ignore_excel_sanitization = $(':input[name=sanitize_excel_export]').is(':checked') ? 0 : 1;
}
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function (data) {
if (data == null) {
ExportError('Export failed (no data provided), please contact your administrator');

View File

@@ -13,7 +13,7 @@ Api documentation can be found here:
https://apereo.github.io/phpCAS/api/
[![Test](https://github.com/apereo/phpCAS/actions/workflows/test.yml/badge.svg)](https://github.com/apereo/phpCAS/actions/workflows/test.yml)
[![Test](https://github.com/EsupPortail/phpCAS/actions/workflows/test.yml/badge.svg)](https://github.com/EsupPortail/phpCAS/actions/workflows/test.yml)
LICENSE
-------

View File

@@ -57,7 +57,7 @@ if (!isset($_SERVER['REQUEST_URI']) && isset($_SERVER['SCRIPT_NAME']) && isset($
/**
* phpCAS version. accessible for the user by phpCAS::getVersion().
*/
define('PHPCAS_VERSION', '1.6.1');
define('PHPCAS_VERSION', '1.6.1+');
/**
* @addtogroup public
@@ -303,7 +303,7 @@ class phpCAS
/**
* This variable is used to enable verbose mode
* This pevents debug info to be show to the user. Since it's a security
* This prevents debug info to be show to the user. Since it's a security
* feature the default is false
*
* @hideinitializer
@@ -338,7 +338,7 @@ class phpCAS
* @param bool $changeSessionID Allow phpCAS to change the session_id
* (Single Sign Out/handleLogoutRequests
* is based on that change)
* @param \SessionHandlerInterface $sessionHandler the session handler
* @param \SessionHandlerInterface|null $sessionHandler the session handler
*
* @return void a newly created CAS_Client object
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
@@ -347,7 +347,7 @@ class phpCAS
*/
public static function client($server_version, $server_hostname,
$server_port, $server_uri, $service_base_url,
$changeSessionID = true, \SessionHandlerInterface $sessionHandler = null
$changeSessionID = true, ?\SessionHandlerInterface $sessionHandler = null
) {
phpCAS :: traceBegin();
if (is_object(self::$_PHPCAS_CLIENT)) {
@@ -393,7 +393,7 @@ class phpCAS
* @param bool $changeSessionID Allow phpCAS to change the session_id
* (Single Sign Out/handleLogoutRequests
* is based on that change)
* @param \SessionHandlerInterface $sessionHandler the session handler
* @param \SessionHandlerInterface|null $sessionHandler the session handler
*
* @return void a newly created CAS_Client object
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
@@ -402,14 +402,14 @@ class phpCAS
*/
public static function proxy($server_version, $server_hostname,
$server_port, $server_uri, $service_base_url,
$changeSessionID = true, \SessionHandlerInterface $sessionHandler = null
$changeSessionID = true, ?\SessionHandlerInterface $sessionHandler = null
) {
phpCAS :: traceBegin();
if (is_object(self::$_PHPCAS_CLIENT)) {
phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')');
}
// store where the initialzer is called from
// store where the initializer is called from
$dbg = debug_backtrace();
self::$_PHPCAS_INIT_CALL = array (
'done' => true,
@@ -560,7 +560,7 @@ class phpCAS
$indent_str .= '| ';
}
// allow for multiline output with proper identing. Usefull for
// allow for multiline output with proper identing. Useful for
// dumping cas answers etc.
$str2 = str_replace("\n", "\n" . self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str, $str);
$str3 = self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2;
@@ -568,7 +568,7 @@ class phpCAS
self::$_PHPCAS_DEBUG['logger']->info($str3);
}
if (!empty(self::$_PHPCAS_DEBUG['filename'])) {
// Check if file exists and modifiy file permissions to be only
// Check if file exists and modify file permissions to be only
// readable by the webserver
if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) {
touch(self::$_PHPCAS_DEBUG['filename']);
@@ -1769,7 +1769,7 @@ class phpCAS
/**
* If you want your service to be proxied you have to enable it (default
* disabled) and define an accepable list of proxies that are allowed to
* disabled) and define an acceptable list of proxies that are allowed to
* proxy your service.
*
* Add each allowed proxy definition object. For the normal CAS_ProxyChain
@@ -1790,7 +1790,7 @@ class phpCAS
* 'http://client.example.com/'
* )));
*
* For quick testing or in certain production screnarios you might want to
* For quick testing or in certain production scenarios you might want to
* allow allow any other valid service to proxy your service. To do so, add
* the "Any" chain:
* phpCAS::allowProxyChain(new CAS_ProxyChain_Any);
@@ -1897,7 +1897,7 @@ class phpCAS
}
/**
* Checks of a proxy client aready exists
* Checks of a proxy client already exists
*
* @throws CAS_OutOfSequenceBeforeProxyException
*

View File

@@ -788,7 +788,7 @@ class CAS_Client
'file' => $dbg[1]['file'],
'line' => $dbg[1]['line'],
'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'],
'result' => (boolean)$auth
'result' => (bool)$auth
);
}
private $_authentication_caller;
@@ -926,7 +926,7 @@ class CAS_Client
* CAS_ServiceBaseUrl_Interface for custom
* behavior. Added in 1.6.0. Similar to
* serverName config in other CAS clients.
* @param \SessionHandlerInterface $sessionHandler the session handler
* @param \SessionHandlerInterface|null $sessionHandler the session handler
*
* @return self a newly created CAS_Client object
*/
@@ -938,7 +938,7 @@ class CAS_Client
$server_uri,
$service_base_url,
$changeSessionID = true,
\SessionHandlerInterface $sessionHandler = null
?\SessionHandlerInterface $sessionHandler = null
) {
// Argument validation
if (gettype($server_version) != 'string')
@@ -3166,7 +3166,7 @@ class CAS_Client
$proxiedService->setCasClient($this);
}
return $proxiedService;
case PHPCAS_PROXIED_SERVICE_IMAP;
case PHPCAS_PROXIED_SERVICE_IMAP:
$proxiedService = new CAS_ProxiedService_Imap($this->_getUser());
if ($proxiedService instanceof CAS_ProxiedService_Testable) {
$proxiedService->setCasClient($this);

View File

@@ -316,7 +316,7 @@ class CAS_PGTStorage_Db extends CAS_PGTStorage_AbstractStorage
try {
$pdo->beginTransaction();
$query = $pdo->query($this->createTableSQL());
$query = $pdo->query($this->createTableSql());
$query->closeCursor();
$pdo->commit();

View File

@@ -127,6 +127,12 @@ class CAS_PGTStorage_File extends CAS_PGTStorage_AbstractStorage
if (!preg_match('`^[a-zA-Z]:`', $path)) {
phpCAS::error('an absolute path is needed for PGT storage to file');
}
// ensure that the directory separator on Windows is '/' for consistency with the rest of the phpcas code
$path = str_replace(DIRECTORY_SEPARATOR , '/', $path);
// store the path (with a trailing '/')
$path = preg_replace('|([^/])$|', '$1/', $path);
} else {

View File

@@ -139,7 +139,12 @@ implements CAS_Request_MultiRequestInterface
$buf = curl_multi_getcontent($handles[$i]);
$request->_storeResponseBody($buf);
curl_multi_remove_handle($multiHandle, $handles[$i]);
curl_close($handles[$i]);
if (PHP_VERSION_ID < 80000) {
curl_close($handles[$i]);
} else {
// unreference it => it will be closed
unset($handles[$i]);
}
}
curl_multi_close($multiHandle);

View File

@@ -86,7 +86,9 @@ implements CAS_Request_RequestInterface
}
// close the CURL session
curl_close($ch);
if (PHP_VERSION_ID < 80000) {
curl_close($ch);
}
phpCAS::traceEnd($res);
return $res;

0
lib/bin/patch-type-declarations Normal file → Executable file
View File

View File

@@ -1,5 +0,0 @@
@ECHO OFF
setlocal DISABLEDELAYEDEXPANSION
SET BIN_TARGET=%~dp0/patch-type-declarations
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
php "%BIN_TARGET%" %*

0
lib/bin/php-parse Normal file → Executable file
View File

View File

@@ -1,5 +0,0 @@
@ECHO OFF
setlocal DISABLEDELAYEDEXPANSION
SET BIN_TARGET=%~dp0/php-parse
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
php "%BIN_TARGET%" %*

0
lib/bin/pscss Normal file → Executable file
View File

View File

@@ -1,5 +0,0 @@
@ECHO OFF
setlocal DISABLEDELAYEDEXPANSION
SET BIN_TARGET=%~dp0/pscss
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
php "%BIN_TARGET%" %*

0
lib/bin/var-dump-server Normal file → Executable file
View File

View File

@@ -1,5 +0,0 @@
@ECHO OFF
setlocal DISABLEDELAYEDEXPANSION
SET BIN_TARGET=%~dp0/var-dump-server
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
php "%BIN_TARGET%" %*

0
lib/bin/yaml-lint Normal file → Executable file
View File

View File

@@ -1,5 +0,0 @@
@ECHO OFF
setlocal DISABLEDELAYEDEXPANSION
SET BIN_TARGET=%~dp0/yaml-lint
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
php "%BIN_TARGET%" %*

View File

@@ -958,6 +958,7 @@ return array(
'PDF417' => $vendorDir . '/tecnickcom/tcpdf/include/barcodes/pdf417.php',
'PDFBulkExport' => $baseDir . '/core/pdfbulkexport.class.inc.php',
'PEAR' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php',
'PEAR_Error' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php',
'PEAR_ErrorStack' => $vendorDir . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php',
'Pelago\\Emogrifier\\Caching\\SimpleStringCache' => $vendorDir . '/pelago/emogrifier/src/Caching/SimpleStringCache.php',
@@ -1645,22 +1646,43 @@ return array(
'Symfony\\Component\\Cache\\Psr16Cache' => $vendorDir . '/symfony/cache/Psr16Cache.php',
'Symfony\\Component\\Cache\\ResettableInterface' => $vendorDir . '/symfony/cache/ResettableInterface.php',
'Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => $vendorDir . '/symfony/cache/Traits/AbstractAdapterTrait.php',
'Symfony\\Component\\Cache\\Traits\\CachedValueInterface' => $vendorDir . '/symfony/cache/Traits/CachedValueInterface.php',
'Symfony\\Component\\Cache\\Traits\\ContractsTrait' => $vendorDir . '/symfony/cache/Traits/ContractsTrait.php',
'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemCommonTrait.php',
'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemTrait.php',
'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => $vendorDir . '/symfony/cache/Traits/ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Redis5Proxy' => $vendorDir . '/symfony/cache/Traits/Redis5Proxy.php',
'Symfony\\Component\\Cache\\Traits\\Redis61ProxyTrait' => $vendorDir . '/symfony/cache/Traits/Redis61ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Redis62ProxyTrait' => $vendorDir . '/symfony/cache/Traits/Redis62ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Redis63ProxyTrait' => $vendorDir . '/symfony/cache/Traits/Redis63ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Redis6Proxy' => $vendorDir . '/symfony/cache/Traits/Redis6Proxy.php',
'Symfony\\Component\\Cache\\Traits\\Redis6ProxyTrait' => $vendorDir . '/symfony/cache/Traits/Redis6ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster5Proxy' => $vendorDir . '/symfony/cache/Traits/RedisCluster5Proxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster61ProxyTrait' => $vendorDir . '/symfony/cache/Traits/RedisCluster61ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster62ProxyTrait' => $vendorDir . '/symfony/cache/Traits/RedisCluster62ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster63ProxyTrait' => $vendorDir . '/symfony/cache/Traits/RedisCluster63ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster6Proxy' => $vendorDir . '/symfony/cache/Traits/RedisCluster6Proxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster6ProxyTrait' => $vendorDir . '/symfony/cache/Traits/RedisCluster6ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterNodeProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisProxy' => $vendorDir . '/symfony/cache/Traits/RedisProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisTrait' => $vendorDir . '/symfony/cache/Traits/RedisTrait.php',
'Symfony\\Component\\Cache\\Traits\\RelayProxy' => $vendorDir . '/symfony/cache/Traits/RelayProxy.php',
'Symfony\\Component\\Cache\\Traits\\RelayProxyTrait' => $vendorDir . '/symfony/cache/Traits/RelayProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\BgsaveTrait' => $vendorDir . '/symfony/cache/Traits/Relay/BgsaveTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\CopyTrait' => $vendorDir . '/symfony/cache/Traits/Relay/CopyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\FtTrait' => $vendorDir . '/symfony/cache/Traits/Relay/FtTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\GeosearchTrait' => $vendorDir . '/symfony/cache/Traits/Relay/GeosearchTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\GetWithMetaTrait' => $vendorDir . '/symfony/cache/Traits/Relay/GetWithMetaTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\GetrangeTrait' => $vendorDir . '/symfony/cache/Traits/Relay/GetrangeTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\HsetTrait' => $vendorDir . '/symfony/cache/Traits/Relay/HsetTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\IsTrackedTrait' => $vendorDir . '/symfony/cache/Traits/Relay/IsTrackedTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\MoveTrait' => $vendorDir . '/symfony/cache/Traits/Relay/MoveTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\NullableReturnTrait' => $vendorDir . '/symfony/cache/Traits/Relay/NullableReturnTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\PfcountTrait' => $vendorDir . '/symfony/cache/Traits/Relay/PfcountTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\Relay11Trait' => $vendorDir . '/symfony/cache/Traits/Relay/Relay11Trait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\Relay121Trait' => $vendorDir . '/symfony/cache/Traits/Relay/Relay121Trait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\Relay12Trait' => $vendorDir . '/symfony/cache/Traits/Relay/Relay12Trait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\Relay20Trait' => $vendorDir . '/symfony/cache/Traits/Relay/Relay20Trait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\SwapdbTrait' => $vendorDir . '/symfony/cache/Traits/Relay/SwapdbTrait.php',
'Symfony\\Component\\Config\\Builder\\ClassBuilder' => $vendorDir . '/symfony/config/Builder/ClassBuilder.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGenerator.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php',
@@ -1825,6 +1847,7 @@ return array(
'Symfony\\Component\\Console\\Helper\\TableRows' => $vendorDir . '/symfony/console/Helper/TableRows.php',
'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php',
'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php',
'Symfony\\Component\\Console\\Helper\\TerminalInputHelper' => $vendorDir . '/symfony/console/Helper/TerminalInputHelper.php',
'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php',
'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php',
'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php',
@@ -2699,7 +2722,6 @@ return array(
'Symfony\\Component\\VarDumper\\Cloner\\Cursor' => $vendorDir . '/symfony/var-dumper/Cloner/Cursor.php',
'Symfony\\Component\\VarDumper\\Cloner\\Data' => $vendorDir . '/symfony/var-dumper/Cloner/Data.php',
'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => $vendorDir . '/symfony/var-dumper/Cloner/DumperInterface.php',
'Symfony\\Component\\VarDumper\\Cloner\\Internal\\NoDefault' => $vendorDir . '/symfony/var-dumper/Cloner/Internal/NoDefault.php',
'Symfony\\Component\\VarDumper\\Cloner\\Stub' => $vendorDir . '/symfony/var-dumper/Cloner/Stub.php',
'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => $vendorDir . '/symfony/var-dumper/Cloner/VarCloner.php',
'Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php',
@@ -2854,6 +2876,7 @@ return array(
'Twig\\ExpressionParser\\InfixExpressionParserInterface' => $vendorDir . '/twig/twig/src/ExpressionParser/InfixExpressionParserInterface.php',
'Twig\\ExpressionParser\\Infix\\ArgumentsTrait' => $vendorDir . '/twig/twig/src/ExpressionParser/Infix/ArgumentsTrait.php',
'Twig\\ExpressionParser\\Infix\\ArrowExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser/Infix/ArrowExpressionParser.php',
'Twig\\ExpressionParser\\Infix\\AssignmentExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser/Infix/AssignmentExpressionParser.php',
'Twig\\ExpressionParser\\Infix\\BinaryOperatorExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser/Infix/BinaryOperatorExpressionParser.php',
'Twig\\ExpressionParser\\Infix\\ConditionalTernaryExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser/Infix/ConditionalTernaryExpressionParser.php',
'Twig\\ExpressionParser\\Infix\\DotExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser/Infix/DotExpressionParser.php',
@@ -2939,10 +2962,15 @@ return array(
'Twig\\Node\\Expression\\Binary\\MulBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/MulBinary.php',
'Twig\\Node\\Expression\\Binary\\NotEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php',
'Twig\\Node\\Expression\\Binary\\NotInBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotInBinary.php',
'Twig\\Node\\Expression\\Binary\\NotSameAsBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotSameAsBinary.php',
'Twig\\Node\\Expression\\Binary\\NullCoalesceBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NullCoalesceBinary.php',
'Twig\\Node\\Expression\\Binary\\ObjectDestructuringSetBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/ObjectDestructuringSetBinary.php',
'Twig\\Node\\Expression\\Binary\\OrBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/OrBinary.php',
'Twig\\Node\\Expression\\Binary\\PowerBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php',
'Twig\\Node\\Expression\\Binary\\RangeBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php',
'Twig\\Node\\Expression\\Binary\\SameAsBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SameAsBinary.php',
'Twig\\Node\\Expression\\Binary\\SequenceDestructuringSetBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SequenceDestructuringSetBinary.php',
'Twig\\Node\\Expression\\Binary\\SetBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SetBinary.php',
'Twig\\Node\\Expression\\Binary\\SpaceshipBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php',
'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php',
'Twig\\Node\\Expression\\Binary\\SubBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SubBinary.php',
@@ -2951,6 +2979,7 @@ return array(
'Twig\\Node\\Expression\\CallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/CallExpression.php',
'Twig\\Node\\Expression\\ConditionalExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConditionalExpression.php',
'Twig\\Node\\Expression\\ConstantExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConstantExpression.php',
'Twig\\Node\\Expression\\EmptyExpression' => $vendorDir . '/twig/twig/src/Node/Expression/EmptyExpression.php',
'Twig\\Node\\Expression\\FilterExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FilterExpression.php',
'Twig\\Node\\Expression\\Filter\\DefaultFilter' => $vendorDir . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php',
'Twig\\Node\\Expression\\Filter\\RawFilter' => $vendorDir . '/twig/twig/src/Node/Expression/Filter/RawFilter.php',

View File

@@ -8,5 +8,4 @@ $baseDir = dirname($vendorDir);
return array(
'Console' => array($vendorDir . '/pear/console_getopt'),
'Archive_Tar' => array($vendorDir . '/pear/archive_tar'),
'' => array($vendorDir . '/pear/pear-core-minimal/src'),
);

View File

@@ -360,10 +360,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
),
);
public static $fallbackDirsPsr0 = array (
0 => __DIR__ . '/..' . '/pear/pear-core-minimal/src',
);
public static $classMap = array (
'AbstractApplicationObjectExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'AbstractApplicationUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
@@ -1317,6 +1313,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'PDF417' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/barcodes/pdf417.php',
'PDFBulkExport' => __DIR__ . '/../..' . '/core/pdfbulkexport.class.inc.php',
'PEAR' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php',
'PEAR_Error' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php',
'PEAR_ErrorStack' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php',
'Pelago\\Emogrifier\\Caching\\SimpleStringCache' => __DIR__ . '/..' . '/pelago/emogrifier/src/Caching/SimpleStringCache.php',
@@ -2004,22 +2001,43 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Cache\\Psr16Cache' => __DIR__ . '/..' . '/symfony/cache/Psr16Cache.php',
'Symfony\\Component\\Cache\\ResettableInterface' => __DIR__ . '/..' . '/symfony/cache/ResettableInterface.php',
'Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/AbstractAdapterTrait.php',
'Symfony\\Component\\Cache\\Traits\\CachedValueInterface' => __DIR__ . '/..' . '/symfony/cache/Traits/CachedValueInterface.php',
'Symfony\\Component\\Cache\\Traits\\ContractsTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ContractsTrait.php',
'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemCommonTrait.php',
'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemTrait.php',
'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Redis5Proxy' => __DIR__ . '/..' . '/symfony/cache/Traits/Redis5Proxy.php',
'Symfony\\Component\\Cache\\Traits\\Redis61ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Redis61ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Redis62ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Redis62ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Redis63ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Redis63ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Redis6Proxy' => __DIR__ . '/..' . '/symfony/cache/Traits/Redis6Proxy.php',
'Symfony\\Component\\Cache\\Traits\\Redis6ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Redis6ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster5Proxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisCluster5Proxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster61ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisCluster61ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster62ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisCluster62ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster63ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisCluster63ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster6Proxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisCluster6Proxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisCluster6ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisCluster6ProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterNodeProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisTrait.php',
'Symfony\\Component\\Cache\\Traits\\RelayProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RelayProxy.php',
'Symfony\\Component\\Cache\\Traits\\RelayProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RelayProxyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\BgsaveTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/BgsaveTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\CopyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/CopyTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\FtTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/FtTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\GeosearchTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/GeosearchTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\GetWithMetaTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/GetWithMetaTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\GetrangeTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/GetrangeTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\HsetTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/HsetTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\IsTrackedTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/IsTrackedTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\MoveTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/MoveTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\NullableReturnTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/NullableReturnTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\PfcountTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/PfcountTrait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\Relay11Trait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/Relay11Trait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\Relay121Trait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/Relay121Trait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\Relay12Trait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/Relay12Trait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\Relay20Trait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/Relay20Trait.php',
'Symfony\\Component\\Cache\\Traits\\Relay\\SwapdbTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/Relay/SwapdbTrait.php',
'Symfony\\Component\\Config\\Builder\\ClassBuilder' => __DIR__ . '/..' . '/symfony/config/Builder/ClassBuilder.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGenerator.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php',
@@ -2184,6 +2202,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Console\\Helper\\TableRows' => __DIR__ . '/..' . '/symfony/console/Helper/TableRows.php',
'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php',
'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php',
'Symfony\\Component\\Console\\Helper\\TerminalInputHelper' => __DIR__ . '/..' . '/symfony/console/Helper/TerminalInputHelper.php',
'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php',
'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php',
'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php',
@@ -3058,7 +3077,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\VarDumper\\Cloner\\Cursor' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Cursor.php',
'Symfony\\Component\\VarDumper\\Cloner\\Data' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Data.php',
'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/DumperInterface.php',
'Symfony\\Component\\VarDumper\\Cloner\\Internal\\NoDefault' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Internal/NoDefault.php',
'Symfony\\Component\\VarDumper\\Cloner\\Stub' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Stub.php',
'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/VarCloner.php',
'Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php',
@@ -3213,6 +3231,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Twig\\ExpressionParser\\InfixExpressionParserInterface' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser/InfixExpressionParserInterface.php',
'Twig\\ExpressionParser\\Infix\\ArgumentsTrait' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser/Infix/ArgumentsTrait.php',
'Twig\\ExpressionParser\\Infix\\ArrowExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser/Infix/ArrowExpressionParser.php',
'Twig\\ExpressionParser\\Infix\\AssignmentExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser/Infix/AssignmentExpressionParser.php',
'Twig\\ExpressionParser\\Infix\\BinaryOperatorExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser/Infix/BinaryOperatorExpressionParser.php',
'Twig\\ExpressionParser\\Infix\\ConditionalTernaryExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser/Infix/ConditionalTernaryExpressionParser.php',
'Twig\\ExpressionParser\\Infix\\DotExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser/Infix/DotExpressionParser.php',
@@ -3298,10 +3317,15 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Twig\\Node\\Expression\\Binary\\MulBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/MulBinary.php',
'Twig\\Node\\Expression\\Binary\\NotEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php',
'Twig\\Node\\Expression\\Binary\\NotInBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotInBinary.php',
'Twig\\Node\\Expression\\Binary\\NotSameAsBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotSameAsBinary.php',
'Twig\\Node\\Expression\\Binary\\NullCoalesceBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NullCoalesceBinary.php',
'Twig\\Node\\Expression\\Binary\\ObjectDestructuringSetBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/ObjectDestructuringSetBinary.php',
'Twig\\Node\\Expression\\Binary\\OrBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/OrBinary.php',
'Twig\\Node\\Expression\\Binary\\PowerBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php',
'Twig\\Node\\Expression\\Binary\\RangeBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php',
'Twig\\Node\\Expression\\Binary\\SameAsBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SameAsBinary.php',
'Twig\\Node\\Expression\\Binary\\SequenceDestructuringSetBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SequenceDestructuringSetBinary.php',
'Twig\\Node\\Expression\\Binary\\SetBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SetBinary.php',
'Twig\\Node\\Expression\\Binary\\SpaceshipBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php',
'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php',
'Twig\\Node\\Expression\\Binary\\SubBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SubBinary.php',
@@ -3310,6 +3334,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Twig\\Node\\Expression\\CallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/CallExpression.php',
'Twig\\Node\\Expression\\ConditionalExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConditionalExpression.php',
'Twig\\Node\\Expression\\ConstantExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConstantExpression.php',
'Twig\\Node\\Expression\\EmptyExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/EmptyExpression.php',
'Twig\\Node\\Expression\\FilterExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FilterExpression.php',
'Twig\\Node\\Expression\\Filter\\DefaultFilter' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php',
'Twig\\Node\\Expression\\Filter\\RawFilter' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Filter/RawFilter.php',
@@ -3542,7 +3567,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
$loader->prefixLengthsPsr4 = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$prefixesPsr0;
$loader->fallbackDirsPsr0 = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$fallbackDirsPsr0;
$loader->classMap = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$classMap;
}, null, ClassLoader::class);

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'combodo/itop',
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'reference' => 'd5706fcbef58868cb8bd6ee6f3af133ca4fdab3e',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -11,18 +11,20 @@
),
'versions' => array(
'apereo/phpcas' => array(
'pretty_version' => '1.6.1',
'version' => '1.6.1.0',
'reference' => 'c129708154852656aabb13d8606cd5b12dbbabac',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '57a7744146a963d8fa80192e0ab351051b711ff6',
'type' => 'library',
'install_path' => __DIR__ . '/../apereo/phpcas',
'aliases' => array(),
'aliases' => array(
0 => '1.3.x-dev',
),
'dev_requirement' => false,
),
'combodo/itop' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'reference' => 'd5706fcbef58868cb8bd6ee6f3af133ca4fdab3e',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -47,77 +49,68 @@
'dev_requirement' => false,
),
'firebase/php-jwt' => array(
'pretty_version' => 'v6.10.0',
'version' => '6.10.0.0',
'reference' => 'a49db6f0a5033aef5143295342f1c95521b075ff',
'pretty_version' => 'v7.0.2',
'version' => '7.0.2.0',
'reference' => '5645b43af647b6947daac1d0f659dd1fbe8d3b65',
'type' => 'library',
'install_path' => __DIR__ . '/../firebase/php-jwt',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/guzzle' => array(
'pretty_version' => '7.8.1',
'version' => '7.8.1.0',
'reference' => '41042bc7ab002487b876a0683fc8dce04ddce104',
'pretty_version' => '7.10.0',
'version' => '7.10.0.0',
'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/promises' => array(
'pretty_version' => '2.0.2',
'version' => '2.0.2.0',
'reference' => 'bbff78d96034045e58e13dedd6ad91b5d1253223',
'pretty_version' => '2.3.0',
'version' => '2.3.0.0',
'reference' => '481557b130ef3790cf82b713667b43030dc9c957',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '2.6.2',
'version' => '2.6.2.0',
'reference' => '45b30f99ac27b5ca93cb4831afe16285f57b8221',
'pretty_version' => '2.8.0',
'version' => '2.8.0.0',
'reference' => '21dc724a0583619cd1652f673303492272778051',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'league/oauth2-client' => array(
'pretty_version' => '2.7.0',
'version' => '2.7.0.0',
'reference' => '160d6274b03562ebeb55ed18399281d8118b76c8',
'pretty_version' => '2.9.0',
'version' => '2.9.0.0',
'reference' => '26e8c5da4f3d78cede7021e09b1330a0fc093d5e',
'type' => 'library',
'install_path' => __DIR__ . '/../league/oauth2-client',
'aliases' => array(),
'dev_requirement' => false,
),
'league/oauth2-google' => array(
'pretty_version' => '4.0.1',
'version' => '4.0.1.0',
'reference' => '1b01ba18ba31b29e88771e3e0979e5c91d4afe76',
'pretty_version' => '4.1.0',
'version' => '4.1.0.0',
'reference' => '8b9bb43740ac6d994aca881a35f7bacbe98c0ffb',
'type' => 'library',
'install_path' => __DIR__ . '/../league/oauth2-google',
'aliases' => array(),
'dev_requirement' => false,
),
'nikic/php-parser' => array(
'pretty_version' => 'v4.18.0',
'version' => '4.18.0.0',
'reference' => '1bcbb2179f97633e98bbbc87044ee2611c7d7999',
'pretty_version' => 'v4.19.5',
'version' => '4.19.5.0',
'reference' => '51bd93cc741b7fc3d63d20b6bdcd99fdaa359837',
'type' => 'library',
'install_path' => __DIR__ . '/../nikic/php-parser',
'aliases' => array(),
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'pretty_version' => 'v9.99.100',
'version' => '9.99.100.0',
'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'dev_requirement' => false,
),
'pear/archive_tar' => array(
'pretty_version' => '1.4.14',
'version' => '1.4.14.0',
@@ -137,9 +130,9 @@
'dev_requirement' => false,
),
'pear/pear-core-minimal' => array(
'pretty_version' => 'v1.10.11',
'version' => '1.10.11.0',
'reference' => '68d0d32ada737153b7e93b8d3c710ebe70ac867d',
'pretty_version' => 'v1.10.18',
'version' => '1.10.18.0',
'reference' => 'c7b55789d01de0ce090d289b73f1bbd6a2f113b1',
'type' => 'library',
'install_path' => __DIR__ . '/../pear/pear-core-minimal',
'aliases' => array(),
@@ -155,9 +148,9 @@
'dev_requirement' => false,
),
'pelago/emogrifier' => array(
'pretty_version' => 'v7.2.0',
'version' => '7.2.0.0',
'reference' => '727bdf7255b51798307f17dec52ff8a91f1c7de3',
'pretty_version' => 'v7.3.0',
'version' => '7.3.0.0',
'reference' => '6e00d9d8235e8cc8eec857e8dcd6cfeefdfd0cd6',
'type' => 'library',
'install_path' => __DIR__ . '/../pelago/emogrifier',
'aliases' => array(),
@@ -224,9 +217,9 @@
),
),
'psr/http-factory' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => 'e616d01114759c4c489f93b099585439f795fe35',
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
@@ -286,22 +279,22 @@
'rsky/pear-core-min' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.10.11',
0 => 'v1.10.18',
),
),
'sabberworm/php-css-parser' => array(
'pretty_version' => '8.4.0',
'version' => '8.4.0.0',
'reference' => 'e41d2140031d533348b2192a83f02d8dd8a71d30',
'pretty_version' => 'v8.9.0',
'version' => '8.9.0.0',
'reference' => 'd8e916507b88e389e26d4ab03c904a082aa66bb9',
'type' => 'library',
'install_path' => __DIR__ . '/../sabberworm/php-css-parser',
'aliases' => array(),
'dev_requirement' => false,
),
'scssphp/scssphp' => array(
'pretty_version' => 'v1.12.1',
'version' => '1.12.1.0',
'reference' => '394ed1e960138710a60d035c1a85d43d0bf0faeb',
'pretty_version' => 'v1.13.0',
'version' => '1.13.0.0',
'reference' => '63d1157457e5554edf00b0c1fabab4c1511d2520',
'type' => 'library',
'install_path' => __DIR__ . '/../scssphp/scssphp',
'aliases' => array(),
@@ -317,9 +310,9 @@
'dev_requirement' => false,
),
'symfony/cache' => array(
'pretty_version' => 'v6.4.12',
'version' => '6.4.12.0',
'reference' => 'a463451b7f6ac4a47b98dbfc78ec2d3560c759d8',
'pretty_version' => 'v6.4.33',
'version' => '6.4.33.0',
'reference' => '5b088fa41eb9568748dc255c45e4054c387ba73b',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/cache',
'aliases' => array(),
@@ -341,45 +334,45 @@
),
),
'symfony/config' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => '5d33e0fb707d603330e0edfd4691803a1253572e',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => 'd445badf0ad2c2a492e38c0378c39997a56ef97b',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/config',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/console' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => '0254811a143e6bc6c8deea08b589a7e68a37f625',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => '0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/css-selector' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => 'd036c6c0d0b09e24a14a35f8292146a658f986e4',
'pretty_version' => 'v6.4.24',
'version' => '6.4.24.0',
'reference' => '9b784413143701aa3c94ac1869a159a9e53e8761',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/css-selector',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/debug-bundle' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => '1e07027423d1d37125b60a50997ada26a9d9d202',
'pretty_version' => 'v6.4.27',
'version' => '6.4.27.0',
'reference' => '21a61c55192d558a6b81cdb12e8c010fc9474fe0',
'type' => 'symfony-bundle',
'install_path' => __DIR__ . '/../symfony/debug-bundle',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/dependency-injection' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => '226ea431b1eda6f0d9f5a4b278757171960bb195',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => 'b17882e933c4c606620247b6708ab53aa3b88753',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/dependency-injection',
'aliases' => array(),
@@ -395,27 +388,27 @@
'dev_requirement' => false,
),
'symfony/dotenv' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => '835f8d2d1022934ac038519de40b88158798c96f',
'pretty_version' => 'v6.4.30',
'version' => '6.4.30.0',
'reference' => '924edbc9631b75302def0258ed1697948b17baf6',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/dotenv',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/error-handler' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => 'c873490a1c97b3a0a4838afc36ff36c112d02788',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => '8c18400784fcb014dc73c8d5601a9576af7f8ad4',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/error-handler',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/event-dispatcher' => array(
'pretty_version' => 'v6.4.25',
'version' => '6.4.25.0',
'reference' => 'b0cf3162020603587363f0551cd3be43958611ff',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => '99d7e101826e6610606b9433248f80c1997cd20b',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/event-dispatcher',
'aliases' => array(),
@@ -437,81 +430,81 @@
),
),
'symfony/filesystem' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => '952a8cb588c3bc6ce76f6023000fb932f16a6e59',
'pretty_version' => 'v6.4.30',
'version' => '6.4.30.0',
'reference' => '441c6b69f7222aadae7cbf5df588496d5ee37789',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/finder' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => '11d736e97f116ac375a81f96e662911a34cd50ce',
'pretty_version' => 'v6.4.33',
'version' => '6.4.33.0',
'reference' => '24965ca011dac87431729640feef8bcf7b5523e0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/finder',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/framework-bundle' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => 'c26a221e0462027d1f9d4a802ed63f8ab07a43d0',
'pretty_version' => 'v6.4.33',
'version' => '6.4.33.0',
'reference' => '9ef2d0b63b9e855ba351e770a603d89699115801',
'type' => 'symfony-bundle',
'install_path' => __DIR__ . '/../symfony/framework-bundle',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/http-foundation' => array(
'pretty_version' => 'v6.4.29',
'version' => '6.4.29.0',
'reference' => 'b03d11e015552a315714c127d8d1e0f9e970ec88',
'pretty_version' => 'v6.4.33',
'version' => '6.4.33.0',
'reference' => 'f1a490cc9d595ba7ebe684220e625d1e472ad278',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-foundation',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/http-kernel' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => '13e8387320b5942d0dc408440c888e2d526efef4',
'pretty_version' => 'v6.4.33',
'version' => '6.4.33.0',
'reference' => '73fa5c999d7f741ca544a97d3c791cc97890ae4d',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-kernel',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/mailer' => array(
'pretty_version' => 'v6.4.25',
'version' => '6.4.25.0',
'reference' => '628b43b45a3e6b15c8a633fb22df547ed9b492a2',
'pretty_version' => 'v6.4.31',
'version' => '6.4.31.0',
'reference' => '8835f93333474780fda1b987cae37e33c3e026ca',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/mailer',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/mime' => array(
'pretty_version' => 'v6.4.24',
'version' => '6.4.24.0',
'reference' => '664d5e844a2de5e11c8255d0aef6bc15a9660ac7',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => '7409686879ca36c09fc970a5fa8ff6e93504dba4',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/mime',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.28.0',
'version' => '1.28.0.0',
'reference' => 'ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb',
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-intl-grapheme' => array(
'pretty_version' => 'v1.28.0',
'version' => '1.28.0.0',
'reference' => '875e90aeea2777b6f135677f618529449334a612',
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => '380872130d3a5dd3ace2f4010d95125fde5d5c70',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
'aliases' => array(),
@@ -554,18 +547,18 @@
'dev_requirement' => false,
),
'symfony/routing' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => '98eab13a07fddc85766f1756129c69f207ffbc21',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => '0dc6253e864e71b486e8ba4970a56ab849106ebe',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/routing',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/runtime' => array(
'pretty_version' => 'v6.4.24',
'version' => '6.4.24.0',
'reference' => 'c1cc6721646f546627236c57f835272806087337',
'pretty_version' => 'v6.4.30',
'version' => '6.4.30.0',
'reference' => 'fb3149ee85d3b639dd3e49ea9dda05656f0537e3',
'type' => 'composer-plugin',
'install_path' => __DIR__ . '/../symfony/runtime',
'aliases' => array(),
@@ -587,54 +580,54 @@
),
),
'symfony/stopwatch' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => 'fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2',
'pretty_version' => 'v6.4.24',
'version' => '6.4.24.0',
'reference' => 'b67e94e06a05d9572c2fa354483b3e13e3cb1898',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/stopwatch',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/string' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => '7cb80bc10bfcdf6b5492741c0b9357dac66940bc',
'pretty_version' => 'v6.4.30',
'version' => '6.4.30.0',
'reference' => '50590a057841fa6bf69d12eceffce3465b9e32cb',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/string',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/translation-contracts' => array(
'pretty_version' => 'v3.4.0',
'version' => '3.4.0.0',
'reference' => 'dee0c6e5b4c07ce851b462530088e64b255ac9c5',
'pretty_version' => 'v3.6.1',
'version' => '3.6.1.0',
'reference' => '65a8bc82080447fae78373aa10f8d13b38338977',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/twig-bridge' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => '142bc3ad4a61d7eedf7cc21d8ef2bd8a8e7417bf',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => '1dcf980dd4f79885b986befdeb1c1bc0d6aedfc8',
'type' => 'symfony-bridge',
'install_path' => __DIR__ . '/../symfony/twig-bridge',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/twig-bundle' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => '35d84393e598dfb774e6a2bf49e5229a8a6dbe4c',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => 'a5c8dcc11a5bf9c96320da20070d2e158a4e0b30',
'type' => 'symfony-bundle',
'install_path' => __DIR__ . '/../symfony/twig-bundle',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/var-dumper' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => '68d6573ec98715ddcae5a0a85bee3c1c27a4c33f',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => '131fc9915e0343052af5ed5040401b481ca192aa',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/var-dumper',
'aliases' => array(),
@@ -650,45 +643,45 @@
'dev_requirement' => false,
),
'symfony/web-profiler-bundle' => array(
'pretty_version' => 'v6.4.2',
'version' => '6.4.2.0',
'reference' => '38462d16856740ec0d1ba2cb902eebf09100dde2',
'pretty_version' => 'v6.4.32',
'version' => '6.4.32.0',
'reference' => '011f59e3f3d20f60d11b4e78b8dc63504f56e145',
'type' => 'symfony-bundle',
'install_path' => __DIR__ . '/../symfony/web-profiler-bundle',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/yaml' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => '4f9237a1bb42455d609e6687d2613dde5b41a587',
'pretty_version' => 'v6.4.30',
'version' => '6.4.30.0',
'reference' => '8207ae83da19ee3748d6d4f567b4d9a7c656e331',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/yaml',
'aliases' => array(),
'dev_requirement' => false,
),
'tecnickcom/tcpdf' => array(
'pretty_version' => '6.10.0',
'version' => '6.10.0.0',
'reference' => 'ca5b6de294512145db96bcbc94e61696599c391d',
'pretty_version' => '6.10.1',
'version' => '6.10.1.0',
'reference' => '7a2701251e5d52fc3d508fd71704683eb54f5939',
'type' => 'library',
'install_path' => __DIR__ . '/../tecnickcom/tcpdf',
'aliases' => array(),
'dev_requirement' => false,
),
'thenetworg/oauth2-azure' => array(
'pretty_version' => 'v2.2.2',
'version' => '2.2.2.0',
'reference' => 'be204a5135f016470a9c33e82ab48785bbc11af2',
'pretty_version' => 'v2.2.4',
'version' => '2.2.4.0',
'reference' => 'a897d60b6b127daa2f27b1b4e62e7af40829d02f',
'type' => 'library',
'install_path' => __DIR__ . '/../thenetworg/oauth2-azure',
'aliases' => array(),
'dev_requirement' => false,
),
'twig/twig' => array(
'pretty_version' => 'v3.21.1',
'version' => '3.21.1.0',
'reference' => '285123877d4dd97dd7c11842ac5fb7e86e60d81d',
'pretty_version' => 'v3.23.0',
'version' => '3.23.0.0',
'reference' => 'a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9',
'type' => 'library',
'install_path' => __DIR__ . '/../twig/twig',
'aliases' => array(),

View File

@@ -1,5 +1,64 @@
# Changelog
## [7.0.2](https://github.com/firebase/php-jwt/compare/v7.0.1...v7.0.2) (2025-12-16)
### Bug Fixes
* add key length validation for ec keys ([#615](https://github.com/firebase/php-jwt/issues/615)) ([7044f9a](https://github.com/firebase/php-jwt/commit/7044f9ae7e7d175d28cca71714feb236f1c0e252))
## [7.0.0](https://github.com/firebase/php-jwt/compare/v6.11.1...v7.0.0) (2025-12-15)
### ⚠️ ⚠️ ⚠️ Security Fixes ⚠️ ⚠️ ⚠️
* add key size validation ([#613](https://github.com/firebase/php-jwt/issues/613)) ([6b80341](https://github.com/firebase/php-jwt/commit/6b80341bf57838ea2d011487917337901cd71576))
**NOTE**: This fix will cause keys with a size below the minimally allowed size to break.
### Features
* add SensitiveParameter attribute to security-critical parameters ([#603](https://github.com/firebase/php-jwt/issues/603)) ([4dbfac0](https://github.com/firebase/php-jwt/commit/4dbfac0260eeb0e9e643063c99998e3219cc539b))
* store timestamp in `ExpiredException` ([#604](https://github.com/firebase/php-jwt/issues/604)) ([f174826](https://github.com/firebase/php-jwt/commit/f1748260d218a856b6a0c23715ac7fae1d7ca95b))
### Bug Fixes
* validate iat and nbf on payload ([#568](https://github.com/firebase/php-jwt/issues/568)) ([953b2c8](https://github.com/firebase/php-jwt/commit/953b2c88bb445b7e3bb82a5141928f13d7343afd))
## [6.11.1](https://github.com/firebase/php-jwt/compare/v6.11.0...v6.11.1) (2025-04-09)
### Bug Fixes
* update error text for consistency ([#528](https://github.com/firebase/php-jwt/issues/528)) ([c11113a](https://github.com/firebase/php-jwt/commit/c11113afa13265e016a669e75494b9203b8a7775))
## [6.11.0](https://github.com/firebase/php-jwt/compare/v6.10.2...v6.11.0) (2025-01-23)
### Features
* support octet typed JWK ([#587](https://github.com/firebase/php-jwt/issues/587)) ([7cb8a26](https://github.com/firebase/php-jwt/commit/7cb8a265fa81edf2fa6ef8098f5bc5ae573c33ad))
### Bug Fixes
* refactor constructor Key to use PHP 8.0 syntax ([#577](https://github.com/firebase/php-jwt/issues/577)) ([29fa2ce](https://github.com/firebase/php-jwt/commit/29fa2ce9e0582cd397711eec1e80c05ce20fabca))
## [6.10.2](https://github.com/firebase/php-jwt/compare/v6.10.1...v6.10.2) (2024-11-24)
### Bug Fixes
* Mitigate PHP8.4 deprecation warnings ([#570](https://github.com/firebase/php-jwt/issues/570)) ([76808fa](https://github.com/firebase/php-jwt/commit/76808fa227f3811aa5cdb3bf81233714b799a5b5))
* support php 8.4 ([#583](https://github.com/firebase/php-jwt/issues/583)) ([e3d68b0](https://github.com/firebase/php-jwt/commit/e3d68b044421339443c74199edd020e03fb1887e))
## [6.10.1](https://github.com/firebase/php-jwt/compare/v6.10.0...v6.10.1) (2024-05-18)
### Bug Fixes
* ensure ratelimit expiry is set every time ([#556](https://github.com/firebase/php-jwt/issues/556)) ([09cb208](https://github.com/firebase/php-jwt/commit/09cb2081c2c3bc0f61e2f2a5fbea5741f7498648))
* ratelimit cache expiration ([#550](https://github.com/firebase/php-jwt/issues/550)) ([dda7250](https://github.com/firebase/php-jwt/commit/dda725033585ece30ff8cae8937320d7e9f18bae))
## [6.10.0](https://github.com/firebase/php-jwt/compare/v6.9.0...v6.10.0) (2023-11-28)

View File

@@ -17,7 +17,7 @@ composer require firebase/php-jwt
```
Optionally, install the `paragonie/sodium_compat` package from composer if your
php is < 7.2 or does not have libsodium installed:
php env does not have libsodium installed:
```bash
composer require paragonie/sodium_compat
@@ -48,7 +48,8 @@ $decoded = JWT::decode($jwt, new Key($key, 'HS256'));
print_r($decoded);
// Pass a stdClass in as the third parameter to get the decoded header values
$decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers = new stdClass());
$headers = new stdClass();
$decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers);
print_r($headers);
/*
@@ -185,7 +186,7 @@ $passphrase = '[YOUR_PASSPHRASE]';
// Can be generated with "ssh-keygen -t rsa -m pem"
$privateKeyFile = '/path/to/key-with-passphrase.pem';
// Create a private key of type "resource"
/** @var OpenSSLAsymmetricKey $privateKey */
$privateKey = openssl_pkey_get_private(
file_get_contents($privateKeyFile),
$passphrase
@@ -290,7 +291,7 @@ $jwks = ['keys' => []];
// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key
// objects. Pass this as the second parameter to JWT::decode.
JWT::decode($payload, JWK::parseKeySet($jwks));
JWT::decode($jwt, JWK::parseKeySet($jwks));
```
Using Cached Key Sets
@@ -349,7 +350,7 @@ use InvalidArgumentException;
use UnexpectedValueException;
try {
$decoded = JWT::decode($payload, $keys);
$decoded = JWT::decode($jwt, $keys);
} catch (InvalidArgumentException $e) {
// provided key/key-array is empty or malformed.
} catch (DomainException $e) {
@@ -379,7 +380,7 @@ like this:
use Firebase\JWT\JWT;
use UnexpectedValueException;
try {
$decoded = JWT::decode($payload, $keys);
$decoded = JWT::decode($jwt, $keys);
} catch (LogicException $e) {
// errors having to do with environmental setup or malformed JWT Keys
} catch (UnexpectedValueException $e) {
@@ -394,7 +395,7 @@ instead, you can do the following:
```php
// return type is stdClass
$decoded = JWT::decode($payload, $keys);
$decoded = JWT::decode($jwt, $keys);
// cast to array
$decoded = json_decode(json_encode($decoded), true);

View File

@@ -20,7 +20,7 @@
],
"license": "BSD-3-Clause",
"require": {
"php": "^7.4||^8.0"
"php": "^8.0"
},
"suggest": {
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
@@ -32,10 +32,10 @@
}
},
"require-dev": {
"guzzlehttp/guzzle": "^6.5||^7.4",
"guzzlehttp/guzzle": "^7.4",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
"psr/cache": "^1.0||^2.0",
"psr/cache": "^2.0||^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
}

View File

@@ -80,9 +80,9 @@ class CachedKeySet implements ArrayAccess
ClientInterface $httpClient,
RequestFactoryInterface $httpFactory,
CacheItemPoolInterface $cache,
int $expiresAfter = null,
?int $expiresAfter = null,
bool $rateLimit = false,
string $defaultAlg = null
?string $defaultAlg = null
) {
$this->jwksUri = $jwksUri;
$this->httpClient = $httpClient;
@@ -180,7 +180,7 @@ class CachedKeySet implements ArrayAccess
$jwksResponse = $this->httpClient->sendRequest($request);
if ($jwksResponse->getStatusCode() !== 200) {
throw new UnexpectedValueException(
sprintf('HTTP Error: %d %s for URI "%s"',
\sprintf('HTTP Error: %d %s for URI "%s"',
$jwksResponse->getStatusCode(),
$jwksResponse->getReasonPhrase(),
$this->jwksUri,
@@ -212,15 +212,21 @@ class CachedKeySet implements ArrayAccess
}
$cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
if (!$cacheItem->isHit()) {
$cacheItem->expiresAfter(1); // # of calls are cached each minute
$cacheItemData = [];
if ($cacheItem->isHit() && \is_array($data = $cacheItem->get())) {
$cacheItemData = $data;
}
$callsPerMinute = (int) $cacheItem->get();
$callsPerMinute = $cacheItemData['callsPerMinute'] ?? 0;
$expiry = $cacheItemData['expiry'] ?? new \DateTime('+60 seconds', new \DateTimeZone('UTC'));
if (++$callsPerMinute > $this->maxCallsPerMinute) {
return true;
}
$cacheItem->set($callsPerMinute);
$cacheItem->set(['expiry' => $expiry, 'callsPerMinute' => $callsPerMinute]);
$cacheItem->expiresAt($expiry);
$this->cache->save($cacheItem);
return false;
}

View File

@@ -6,6 +6,8 @@ class ExpiredException extends \UnexpectedValueException implements JWTException
{
private object $payload;
private ?int $timestamp = null;
public function setPayload(object $payload): void
{
$this->payload = $payload;
@@ -15,4 +17,14 @@ class ExpiredException extends \UnexpectedValueException implements JWTException
{
return $this->payload;
}
public function setTimestamp(int $timestamp): void
{
$this->timestamp = $timestamp;
}
public function getTimestamp(): ?int
{
return $this->timestamp;
}
}

View File

@@ -52,7 +52,7 @@ class JWK
*
* @uses parseKey
*/
public static function parseKeySet(array $jwks, string $defaultAlg = null): array
public static function parseKeySet(#[\SensitiveParameter] array $jwks, ?string $defaultAlg = null): array
{
$keys = [];
@@ -93,7 +93,7 @@ class JWK
*
* @uses createPemFromModulusAndExponent
*/
public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
public static function parseKey(#[\SensitiveParameter] array $jwk, ?string $defaultAlg = null): ?Key
{
if (empty($jwk)) {
throw new InvalidArgumentException('JWK must not be empty');
@@ -172,6 +172,12 @@ class JWK
// This library works internally with EdDSA keys (Ed25519) encoded in standard base64.
$publicKey = JWT::convertBase64urlToBase64($jwk['x']);
return new Key($publicKey, $jwk['alg']);
case 'oct':
if (!isset($jwk['k'])) {
throw new UnexpectedValueException('k not set');
}
return new Key(JWT::urlsafeB64Decode($jwk['k']), $jwk['alg']);
default:
break;
}
@@ -212,7 +218,7 @@ class JWK
)
);
return sprintf(
return \sprintf(
"-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
wordwrap(base64_encode($pem), 64, "\n", true)
);

View File

@@ -31,6 +31,8 @@ class JWT
private const ASN1_SEQUENCE = 0x10;
private const ASN1_BIT_STRING = 0x03;
private const RSA_KEY_MIN_LENGTH=2048;
/**
* When checking nbf, iat or expiration times,
* we want to provide some extra leeway time to
@@ -95,8 +97,8 @@ class JWT
*/
public static function decode(
string $jwt,
$keyOrKeyArray,
stdClass &$headers = null
#[\SensitiveParameter] $keyOrKeyArray,
?stdClass &$headers = null
): stdClass {
// Validate JWT
$timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
@@ -127,6 +129,16 @@ class JWT
if (!$payload instanceof stdClass) {
throw new UnexpectedValueException('Payload must be a JSON object');
}
if (isset($payload->iat) && !\is_numeric($payload->iat)) {
throw new UnexpectedValueException('Payload iat must be a number');
}
if (isset($payload->nbf) && !\is_numeric($payload->nbf)) {
throw new UnexpectedValueException('Payload nbf must be a number');
}
if (isset($payload->exp) && !\is_numeric($payload->exp)) {
throw new UnexpectedValueException('Payload exp must be a number');
}
$sig = static::urlsafeB64Decode($cryptob64);
if (empty($header->alg)) {
throw new UnexpectedValueException('Empty algorithm');
@@ -154,7 +166,7 @@ class JWT
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) {
$ex = new BeforeValidException(
'Cannot handle token with nbf prior to ' . \date(DateTime::ISO8601, (int) $payload->nbf)
'Cannot handle token with nbf prior to ' . \date(DateTime::ATOM, (int) floor($payload->nbf))
);
$ex->setPayload($payload);
throw $ex;
@@ -165,7 +177,7 @@ class JWT
// correctly used the nbf claim).
if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) {
$ex = new BeforeValidException(
'Cannot handle token with iat prior to ' . \date(DateTime::ISO8601, (int) $payload->iat)
'Cannot handle token with iat prior to ' . \date(DateTime::ATOM, (int) floor($payload->iat))
);
$ex->setPayload($payload);
throw $ex;
@@ -175,6 +187,7 @@ class JWT
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
$ex = new ExpiredException('Expired token');
$ex->setPayload($payload);
$ex->setTimestamp($timestamp);
throw $ex;
}
@@ -185,7 +198,7 @@ class JWT
* Converts and signs a PHP array into a JWT string.
*
* @param array<mixed> $payload PHP array
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
* @param string $keyId
@@ -198,13 +211,13 @@ class JWT
*/
public static function encode(
array $payload,
$key,
#[\SensitiveParameter] $key,
string $alg,
string $keyId = null,
array $head = null
?string $keyId = null,
?array $head = null
): string {
$header = ['typ' => 'JWT'];
if (isset($head) && \is_array($head)) {
if (isset($head)) {
$header = \array_merge($header, $head);
}
$header['alg'] = $alg;
@@ -226,7 +239,7 @@ class JWT
* Sign a string with a given key and algorithm.
*
* @param string $msg The message to sign
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256',
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
*
@@ -236,7 +249,7 @@ class JWT
*/
public static function sign(
string $msg,
$key,
#[\SensitiveParameter] $key,
string $alg
): string {
if (empty(static::$supported_algs[$alg])) {
@@ -248,10 +261,19 @@ class JWT
if (!\is_string($key)) {
throw new InvalidArgumentException('key must be a string when using hmac');
}
self::validateHmacKeyLength($key, $algorithm);
return \hash_hmac($algorithm, $msg, $key, true);
case 'openssl':
$signature = '';
$success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line
if (!$key = openssl_pkey_get_private($key)) {
throw new DomainException('OpenSSL unable to validate key');
}
if (str_starts_with($alg, 'RS')) {
self::validateRsaKeyLength($key);
} elseif (str_starts_with($alg, 'ES')) {
self::validateEcKeyLength($key, $alg);
}
$success = \openssl_sign($msg, $signature, $key, $algorithm);
if (!$success) {
throw new DomainException('OpenSSL unable to sign data');
}
@@ -290,7 +312,7 @@ class JWT
*
* @param string $msg The original message (header and body)
* @param string $signature The original signature
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
* @param string $alg The algorithm
*
* @return bool
@@ -300,7 +322,7 @@ class JWT
private static function verify(
string $msg,
string $signature,
$keyMaterial,
#[\SensitiveParameter] $keyMaterial,
string $alg
): bool {
if (empty(static::$supported_algs[$alg])) {
@@ -310,7 +332,15 @@ class JWT
list($function, $algorithm) = static::$supported_algs[$alg];
switch ($function) {
case 'openssl':
$success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line
if (!$key = openssl_pkey_get_public($keyMaterial)) {
throw new DomainException('OpenSSL unable to validate key');
}
if (str_starts_with($alg, 'RS')) {
self::validateRsaKeyLength($key);
} elseif (str_starts_with($alg, 'ES')) {
self::validateEcKeyLength($key, $alg);
}
$success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm);
if ($success === 1) {
return true;
}
@@ -347,6 +377,7 @@ class JWT
if (!\is_string($keyMaterial)) {
throw new InvalidArgumentException('key must be a string when using hmac');
}
self::validateHmacKeyLength($keyMaterial, $algorithm);
$hash = \hash_hmac($algorithm, $msg, $keyMaterial, true);
return self::constantTimeEquals($hash, $signature);
}
@@ -384,12 +415,7 @@ class JWT
*/
public static function jsonEncode(array $input): string
{
if (PHP_VERSION_ID >= 50400) {
$json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
} else {
// PHP 5.3 only
$json = \json_encode($input);
}
$json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
if ($errno = \json_last_error()) {
self::handleJsonError($errno);
} elseif ($json === 'null') {
@@ -459,7 +485,7 @@ class JWT
* @return Key
*/
private static function getKey(
$keyOrKeyArray,
#[\SensitiveParameter] $keyOrKeyArray,
?string $kid
): Key {
if ($keyOrKeyArray instanceof Key) {
@@ -666,4 +692,57 @@ class JWT
return [$pos, $data];
}
/**
* Validate HMAC key length
*
* @param string $key HMAC key material
* @param string $algorithm The algorithm
*
* @throws DomainException Provided key is too short
*/
private static function validateHmacKeyLength(string $key, string $algorithm): void
{
$keyLength = \strlen($key) * 8;
$minKeyLength = (int) \str_replace('SHA', '', $algorithm);
if ($keyLength < $minKeyLength) {
throw new DomainException('Provided key is too short');
}
}
/**
* Validate RSA key length
*
* @param OpenSSLAsymmetricKey $key RSA key material
* @throws DomainException Provided key is too short
*/
private static function validateRsaKeyLength(#[\SensitiveParameter] OpenSSLAsymmetricKey $key): void
{
if (!$keyDetails = openssl_pkey_get_details($key)) {
throw new DomainException('Unable to validate key');
}
if ($keyDetails['bits'] < self::RSA_KEY_MIN_LENGTH) {
throw new DomainException('Provided key is too short');
}
}
/**
* Validate RSA key length
*
* @param OpenSSLAsymmetricKey $key RSA key material
* @param string $algorithm The algorithm
* @throws DomainException Provided key is too short
*/
private static function validateEcKeyLength(
#[\SensitiveParameter] OpenSSLAsymmetricKey $key,
string $algorithm
): void {
if (!$keyDetails = openssl_pkey_get_details($key)) {
throw new DomainException('Unable to validate key');
}
$minKeyLength = (int) \str_replace('ES', '', $algorithm);
if ($keyDetails['bits'] < $minKeyLength) {
throw new DomainException('Provided key is too short');
}
}
}

View File

@@ -9,26 +9,20 @@ use TypeError;
class Key
{
/** @var string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate */
private $keyMaterial;
/** @var string */
private $algorithm;
/**
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial
* @param string $algorithm
*/
public function __construct(
$keyMaterial,
string $algorithm
#[\SensitiveParameter] private $keyMaterial,
private string $algorithm
) {
if (
!\is_string($keyMaterial)
&& !$keyMaterial instanceof OpenSSLAsymmetricKey
&& !$keyMaterial instanceof OpenSSLCertificate
&& !\is_resource($keyMaterial)
) {
throw new TypeError('Key material must be a string, resource, or OpenSSLAsymmetricKey');
throw new TypeError('Key material must be a string, OpenSSLCertificate, or OpenSSLAsymmetricKey');
}
if (empty($keyMaterial)) {
@@ -38,10 +32,6 @@ class Key
if (empty($algorithm)) {
throw new InvalidArgumentException('Algorithm must not be empty');
}
// TODO: Remove in PHP 8.0 in favor of class constructor property promotion
$this->keyMaterial = $keyMaterial;
$this->algorithm = $algorithm;
}
/**
@@ -55,7 +45,7 @@ class Key
}
/**
* @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate
* @return string|OpenSSLAsymmetricKey|OpenSSLCertificate
*/
public function getKeyMaterial()
{

View File

@@ -2,6 +2,56 @@
Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
## 7.10.0 - 2025-08-23
### Added
- Support for PHP 8.5
### Changed
- Adjusted `guzzlehttp/promises` version constraint to `^2.3`
- Adjusted `guzzlehttp/psr7` version constraint to `^2.8`
## 7.9.3 - 2025-03-27
### Changed
- Remove explicit content-length header for GET requests
- Improve compatibility with bad servers for boolean cookie values
## 7.9.2 - 2024-07-24
### Fixed
- Adjusted handler selection to use cURL if its version is 7.21.2 or higher, rather than 7.34.0
## 7.9.1 - 2024-07-19
### Fixed
- Fix TLS 1.3 check for HTTP/2 requests
## 7.9.0 - 2024-07-18
### Changed
- Improve protocol version checks to provide feedback around unsupported protocols
- Only select the cURL handler by default if 7.34.0 or higher is linked
- Improved `CurlMultiHandler` to avoid busy wait if possible
- Dropped support for EOL `guzzlehttp/psr7` v1
- Improved URI user info redaction in errors
## 7.8.2 - 2024-07-18
### Added
- Support for PHP 8.4
## 7.8.1 - 2023-12-03

View File

@@ -62,11 +62,11 @@ composer require guzzlehttp/guzzle
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
|---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------|
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 |
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 |
| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 |
| 6.x | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 |
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.4 |
| 3.x | EOL (2016-10-31) | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 |
| 4.x | EOL (2016-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 |
| 5.x | EOL (2019-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 |
| 6.x | EOL (2023-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 |
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.5 |
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x

View File

@@ -50,11 +50,39 @@
"homepage": "https://github.com/Tobion"
}
],
"repositories": [
{
"type": "package",
"package": {
"name": "guzzle/client-integration-tests",
"version": "v3.0.2",
"dist": {
"url": "https://codeload.github.com/guzzle/client-integration-tests/zip/2c025848417c1135031fdf9c728ee53d0a7ceaee",
"type": "zip"
},
"require": {
"php": "^7.2.5 || ^8.0",
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.11",
"php-http/message": "^1.0 || ^2.0",
"guzzlehttp/psr7": "^1.7 || ^2.0",
"th3n3rd/cartesian-product": "^0.3"
},
"autoload": {
"psr-4": {
"Http\\Client\\Tests\\": "src/"
}
},
"bin": [
"bin/http_test_server"
]
}
}
],
"require": {
"php": "^7.2.5 || ^8.0",
"ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.1",
"guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
"guzzlehttp/promises": "^2.3",
"guzzlehttp/psr7": "^2.8",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
@@ -64,9 +92,9 @@
"require-dev": {
"ext-curl": "*",
"bamarni/composer-bin-plugin": "^1.8.2",
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
"guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.36 || ^9.6.15",
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {

View File

@@ -11,7 +11,7 @@ final class BodySummarizer implements BodySummarizerInterface
*/
private $truncateAt;
public function __construct(int $truncateAt = null)
public function __construct(?int $truncateAt = null)
{
$this->truncateAt = $truncateAt;
}
@@ -22,7 +22,7 @@ final class BodySummarizer implements BodySummarizerInterface
public function summarize(MessageInterface $message): ?string
{
return $this->truncateAt === null
? \GuzzleHttp\Psr7\Message::bodySummary($message)
: \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt);
? Psr7\Message::bodySummary($message)
: Psr7\Message::bodySummary($message, $this->truncateAt);
}
}

View File

@@ -52,7 +52,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
*
* @param array $config Client configuration settings.
*
* @see \GuzzleHttp\RequestOptions for a list of available request options.
* @see RequestOptions for a list of available request options.
*/
public function __construct(array $config = [])
{
@@ -202,7 +202,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
*
* @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
*/
public function getConfig(string $option = null)
public function getConfig(?string $option = null)
{
return $option === null
? $this->config

View File

@@ -80,5 +80,5 @@ interface ClientInterface
*
* @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0.
*/
public function getConfig(string $option = null);
public function getConfig(?string $option = null);
}

View File

@@ -103,7 +103,7 @@ class CookieJar implements CookieJarInterface
}, $this->getIterator()->getArrayCopy());
}
public function clear(string $domain = null, string $path = null, string $name = null): void
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void
{
if (!$domain) {
$this->cookies = [];

View File

@@ -62,7 +62,7 @@ interface CookieJarInterface extends \Countable, \IteratorAggregate
* @param string|null $path Clears cookies matching a domain and path
* @param string|null $name Clears cookies matching a domain, path, and name
*/
public function clear(string $domain = null, string $path = null, string $name = null): void;
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void;
/**
* Discard all sessions cookies.

View File

@@ -62,6 +62,10 @@ class SetCookie
if (is_numeric($value)) {
$data[$search] = (int) $value;
}
} elseif ($search === 'Secure' || $search === 'Discard' || $search === 'HttpOnly') {
if ($value) {
$data[$search] = true;
}
} else {
$data[$search] = $value;
}

View File

@@ -14,7 +14,7 @@ class BadResponseException extends RequestException
string $message,
RequestInterface $request,
ResponseInterface $response,
\Throwable $previous = null,
?\Throwable $previous = null,
array $handlerContext = []
) {
parent::__construct($message, $request, $response, $previous, $handlerContext);

View File

@@ -25,7 +25,7 @@ class ConnectException extends TransferException implements NetworkExceptionInte
public function __construct(
string $message,
RequestInterface $request,
\Throwable $previous = null,
?\Throwable $previous = null,
array $handlerContext = []
) {
parent::__construct($message, 0, $previous);

View File

@@ -7,7 +7,6 @@ use GuzzleHttp\BodySummarizerInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
/**
* HTTP Request exception
@@ -32,8 +31,8 @@ class RequestException extends TransferException implements RequestExceptionInte
public function __construct(
string $message,
RequestInterface $request,
ResponseInterface $response = null,
\Throwable $previous = null,
?ResponseInterface $response = null,
?\Throwable $previous = null,
array $handlerContext = []
) {
// Set the code of the exception if the response is set and not future.
@@ -63,10 +62,10 @@ class RequestException extends TransferException implements RequestExceptionInte
*/
public static function create(
RequestInterface $request,
ResponseInterface $response = null,
\Throwable $previous = null,
?ResponseInterface $response = null,
?\Throwable $previous = null,
array $handlerContext = [],
BodySummarizerInterface $bodySummarizer = null
?BodySummarizerInterface $bodySummarizer = null
): self {
if (!$response) {
return new self(
@@ -90,8 +89,7 @@ class RequestException extends TransferException implements RequestExceptionInte
$className = __CLASS__;
}
$uri = $request->getUri();
$uri = static::obfuscateUri($uri);
$uri = \GuzzleHttp\Psr7\Utils::redactUserInfo($request->getUri());
// Client Error: `GET /` resulted in a `404 Not Found` response:
// <html> ... (truncated)
@@ -113,20 +111,6 @@ class RequestException extends TransferException implements RequestExceptionInte
return new $className($message, $request, $response, $previous, $handlerContext);
}
/**
* Obfuscates URI if there is a username and a password present
*/
private static function obfuscateUri(UriInterface $uri): UriInterface
{
$userInfo = $uri->getUserInfo();
if (false !== ($pos = \strpos($userInfo, ':'))) {
return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
}
return $uri;
}
/**
* Get the request that caused the exception
*/

View File

@@ -11,6 +11,7 @@ use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\TransferStats;
use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/**
* Creates curl resources from a request
@@ -46,6 +47,16 @@ class CurlFactory implements CurlFactoryInterface
public function create(RequestInterface $request, array $options): EasyHandle
{
$protocolVersion = $request->getProtocolVersion();
if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
if (!self::supportsHttp2()) {
throw new ConnectException('HTTP/2 is supported by the cURL handler, however libcurl is built without HTTP/2 support.', $request);
}
} elseif ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
throw new ConnectException(sprintf('HTTP/%s is not supported by the cURL handler.', $protocolVersion), $request);
}
if (isset($options['curl']['body_as_string'])) {
$options['_body_as_string'] = $options['curl']['body_as_string'];
unset($options['curl']['body_as_string']);
@@ -72,13 +83,51 @@ class CurlFactory implements CurlFactoryInterface
return $easy;
}
private static function supportsHttp2(): bool
{
static $supportsHttp2 = null;
if (null === $supportsHttp2) {
$supportsHttp2 = self::supportsTls12()
&& defined('CURL_VERSION_HTTP2')
&& (\CURL_VERSION_HTTP2 & \curl_version()['features']);
}
return $supportsHttp2;
}
private static function supportsTls12(): bool
{
static $supportsTls12 = null;
if (null === $supportsTls12) {
$supportsTls12 = \CURL_SSLVERSION_TLSv1_2 & \curl_version()['features'];
}
return $supportsTls12;
}
private static function supportsTls13(): bool
{
static $supportsTls13 = null;
if (null === $supportsTls13) {
$supportsTls13 = defined('CURL_SSLVERSION_TLSv1_3')
&& (\CURL_SSLVERSION_TLSv1_3 & \curl_version()['features']);
}
return $supportsTls13;
}
public function release(EasyHandle $easy): void
{
$resource = $easy->handle;
unset($easy->handle);
if (\count($this->handles) >= $this->maxHandles) {
\curl_close($resource);
if (PHP_VERSION_ID < 80000) {
\curl_close($resource);
}
} else {
// Remove all callback functions as they can hold onto references
// and are not cleaned up by curl_reset. Using curl_setopt_array
@@ -147,7 +196,7 @@ class CurlFactory implements CurlFactoryInterface
'error' => \curl_error($easy->handle),
'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME),
] + \curl_getinfo($easy->handle);
$ctx[self::CURL_VERSION_STR] = \curl_version()['version'];
$ctx[self::CURL_VERSION_STR] = self::getCurlVersion();
$factory->release($easy);
// Retry when nothing is present or when curl failed to rewind.
@@ -158,6 +207,17 @@ class CurlFactory implements CurlFactoryInterface
return self::createRejection($easy, $ctx);
}
private static function getCurlVersion(): string
{
static $curlVersion = null;
if (null === $curlVersion) {
$curlVersion = \curl_version()['version'];
}
return $curlVersion;
}
private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface
{
static $connectionErrors = [
@@ -194,15 +254,22 @@ class CurlFactory implements CurlFactoryInterface
);
}
$uri = $easy->request->getUri();
$sanitizedError = self::sanitizeCurlError($ctx['error'] ?? '', $uri);
$message = \sprintf(
'cURL error %s: %s (%s)',
$ctx['errno'],
$ctx['error'],
$sanitizedError,
'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
);
$uriString = (string) $easy->request->getUri();
if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) {
$message .= \sprintf(' for %s', $uriString);
if ('' !== $sanitizedError) {
$redactedUriString = \GuzzleHttp\Psr7\Utils::redactUserInfo($uri)->__toString();
if ($redactedUriString !== '' && false === \strpos($sanitizedError, $redactedUriString)) {
$message .= \sprintf(' for %s', $redactedUriString);
}
}
// Create a connection exception if it was a specific error code.
@@ -213,6 +280,24 @@ class CurlFactory implements CurlFactoryInterface
return P\Create::rejectionFor($error);
}
private static function sanitizeCurlError(string $error, UriInterface $uri): string
{
if ('' === $error) {
return $error;
}
$baseUri = $uri->withQuery('')->withFragment('');
$baseUriString = $baseUri->__toString();
if ('' === $baseUriString) {
return $error;
}
$redactedUriString = \GuzzleHttp\Psr7\Utils::redactUserInfo($baseUri)->__toString();
return str_replace($baseUriString, $redactedUriString, $error);
}
/**
* @return array<int|string, mixed>
*/
@@ -232,10 +317,11 @@ class CurlFactory implements CurlFactoryInterface
}
$version = $easy->request->getProtocolVersion();
if ($version == 1.1) {
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
} elseif ($version == 2.0) {
if ('2' === $version || '2.0' === $version) {
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
} elseif ('1.1' === $version) {
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
} else {
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
}
@@ -390,8 +476,10 @@ class CurlFactory implements CurlFactoryInterface
// The empty string enables all available decoders and implicitly
// sets a matching 'Accept-Encoding' header.
$conf[\CURLOPT_ENCODING] = '';
// But as the user did not specify any acceptable encodings we need
// to overwrite this implicit header with an empty one.
// But as the user did not specify any encoding preference,
// let's leave it up to server by preventing curl from sending
// the header, which will be interpreted as 'Accept-Encoding: *'.
// https://www.rfc-editor.org/rfc/rfc9110#field.accept-encoding
$conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
}
}
@@ -455,23 +543,35 @@ class CurlFactory implements CurlFactoryInterface
}
if (isset($options['crypto_method'])) {
if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_0')) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.0 not supported by your version of cURL');
$protocolVersion = $easy->request->getProtocolVersion();
// If HTTP/2, upgrade TLS 1.0 and 1.1 to 1.2
if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
if (
\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']
|| \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']
|| \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']
) {
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
} elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
if (!self::supportsTls13()) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
} else {
throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
}
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0;
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_1')) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.1 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1;
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_2')) {
if (!self::supportsTls12()) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
} elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_3')) {
if (!self::supportsTls13()) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
@@ -631,7 +731,10 @@ class CurlFactory implements CurlFactoryInterface
public function __destruct()
{
foreach ($this->handles as $id => $handle) {
\curl_close($handle);
if (PHP_VERSION_ID < 80000) {
\curl_close($handle);
}
unset($this->handles[$id]);
}
}

View File

@@ -2,6 +2,7 @@
namespace GuzzleHttp\Handler;
use Closure;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
@@ -159,6 +160,9 @@ class CurlMultiHandler
}
}
// Run curl_multi_exec in the queue to enable other async tasks to run
P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue']));
// Step through the task queue which may add additional requests.
P\Utils::queue()->run();
@@ -169,11 +173,24 @@ class CurlMultiHandler
}
while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
// Prevent busy looping for slow HTTP requests.
\curl_multi_select($this->_mh, $this->selectTimeout);
}
$this->processMessages();
}
/**
* Runs \curl_multi_exec() inside the event loop, to prevent busy looping
*/
private function tickInQueue(): void
{
if (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
\curl_multi_select($this->_mh, 0);
P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue']));
}
}
/**
* Runs until all outstanding connections have completed.
*/
@@ -223,7 +240,10 @@ class CurlMultiHandler
$handle = $this->handles[$id]['easy']->handle;
unset($this->delays[$id], $this->handles[$id]);
\curl_multi_remove_handle($this->_mh, $handle);
\curl_close($handle);
if (PHP_VERSION_ID < 80000) {
\curl_close($handle);
}
return true;
}

View File

@@ -52,21 +52,21 @@ class MockHandler implements \Countable
* @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
* @param callable|null $onRejected Callback to invoke when the return value is rejected.
*/
public static function createWithMiddleware(array $queue = null, callable $onFulfilled = null, callable $onRejected = null): HandlerStack
public static function createWithMiddleware(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null): HandlerStack
{
return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
}
/**
* The passed in value must be an array of
* {@see \Psr\Http\Message\ResponseInterface} objects, Exceptions,
* {@see ResponseInterface} objects, Exceptions,
* callables, or Promises.
*
* @param array<int, mixed>|null $queue The parameters to be passed to the append function, as an indexed array.
* @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
* @param callable|null $onRejected Callback to invoke when the return value is rejected.
*/
public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null)
public function __construct(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null)
{
$this->onFulfilled = $onFulfilled;
$this->onRejected = $onRejected;
@@ -200,7 +200,7 @@ class MockHandler implements \Countable
private function invokeStats(
RequestInterface $request,
array $options,
ResponseInterface $response = null,
?ResponseInterface $response = null,
$reason = null
): void {
if (isset($options['on_stats'])) {

View File

@@ -17,10 +17,10 @@ class Proxy
* Sends synchronous requests to a specific handler while sending all other
* requests to another handler.
*
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for normal responses
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $sync Handler used for synchronous responses.
* @param callable(RequestInterface, array): PromiseInterface $default Handler used for normal responses
* @param callable(RequestInterface, array): PromiseInterface $sync Handler used for synchronous responses.
*
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
* @return callable(RequestInterface, array): PromiseInterface Returns the composed handler.
*/
public static function wrapSync(callable $default, callable $sync): callable
{
@@ -37,10 +37,10 @@ class Proxy
* performance benefits of curl while still supporting true streaming
* through the StreamHandler.
*
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for non-streaming responses
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $streaming Handler used for streaming responses
* @param callable(RequestInterface, array): PromiseInterface $default Handler used for non-streaming responses
* @param callable(RequestInterface, array): PromiseInterface $streaming Handler used for streaming responses
*
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
* @return callable(RequestInterface, array): PromiseInterface Returns the composed handler.
*/
public static function wrapStreaming(callable $default, callable $streaming): callable
{

View File

@@ -40,6 +40,12 @@ class StreamHandler
\usleep($options['delay'] * 1000);
}
$protocolVersion = $request->getProtocolVersion();
if ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
throw new ConnectException(sprintf('HTTP/%s is not supported by the stream handler.', $protocolVersion), $request);
}
$startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
try {
@@ -47,8 +53,14 @@ class StreamHandler
$request = $request->withoutHeader('Expect');
// Append a content-length header if body size is zero to match
// cURL's behavior.
if (0 === $request->getBody()->getSize()) {
// the behavior of `CurlHandler`
if (
(
0 === \strcasecmp('PUT', $request->getMethod())
|| 0 === \strcasecmp('POST', $request->getMethod())
)
&& 0 === $request->getBody()->getSize()
) {
$request = $request->withHeader('Content-Length', '0');
}
@@ -83,8 +95,8 @@ class StreamHandler
array $options,
RequestInterface $request,
?float $startTime,
ResponseInterface $response = null,
\Throwable $error = null
?ResponseInterface $response = null,
?\Throwable $error = null
): void {
if (isset($options['on_stats'])) {
$stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []);
@@ -273,7 +285,7 @@ class StreamHandler
// HTTP/1.1 streams using the PHP stream wrapper require a
// Connection: close header
if ($request->getProtocolVersion() == '1.1'
if ($request->getProtocolVersion() === '1.1'
&& !$request->hasHeader('Connection')
) {
$request = $request->withHeader('Connection', 'close');
@@ -321,8 +333,15 @@ class StreamHandler
);
return $this->createResource(
function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) {
function () use ($uri, $contextResource, $context, $options, $request) {
$resource = @\fopen((string) $uri, 'r', false, $contextResource);
// See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable
if (function_exists('http_get_last_response_headers')) {
/** @var array|null */
$http_response_header = \http_get_last_response_headers();
}
$this->lastHeaders = $http_response_header ?? [];
if (false === $resource) {

View File

@@ -44,7 +44,7 @@ class HandlerStack
* handler is provided, the best handler for your
* system will be utilized.
*/
public static function create(callable $handler = null): self
public static function create(?callable $handler = null): self
{
$stack = new self($handler ?: Utils::chooseHandler());
$stack->push(Middleware::httpErrors(), 'http_errors');
@@ -58,7 +58,7 @@ class HandlerStack
/**
* @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler.
*/
public function __construct(callable $handler = null)
public function __construct(?callable $handler = null)
{
$this->handler = $handler;
}
@@ -131,7 +131,7 @@ class HandlerStack
* @param callable(callable): callable $middleware Middleware function
* @param string $name Name to register for this middleware.
*/
public function unshift(callable $middleware, string $name = null): void
public function unshift(callable $middleware, ?string $name = null): void
{
\array_unshift($this->stack, [$middleware, $name]);
$this->cached = null;

View File

@@ -68,7 +68,7 @@ class MessageFormatter implements MessageFormatterInterface
* @param ResponseInterface|null $response Response that was received
* @param \Throwable|null $error Exception that was received
*/
public function format(RequestInterface $request, ResponseInterface $response = null, \Throwable $error = null): string
public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string
{
$cache = [];

View File

@@ -14,5 +14,5 @@ interface MessageFormatterInterface
* @param ResponseInterface|null $response Response that was received
* @param \Throwable|null $error Exception that was received
*/
public function format(RequestInterface $request, ResponseInterface $response = null, \Throwable $error = null): string;
public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string;
}

View File

@@ -55,7 +55,7 @@ final class Middleware
*
* @return callable(callable): callable Returns a function that accepts the next handler.
*/
public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable
public static function httpErrors(?BodySummarizerInterface $bodySummarizer = null): callable
{
return static function (callable $handler) use ($bodySummarizer): callable {
return static function ($request, array $options) use ($handler, $bodySummarizer) {
@@ -132,7 +132,7 @@ final class Middleware
*
* @return callable Returns a function that accepts the next handler.
*/
public static function tap(callable $before = null, callable $after = null): callable
public static function tap(?callable $before = null, ?callable $after = null): callable
{
return static function (callable $handler) use ($before, $after): callable {
return static function (RequestInterface $request, array $options) use ($handler, $before, $after) {
@@ -176,7 +176,7 @@ final class Middleware
*
* @return callable Returns a function that accepts the next handler.
*/
public static function retry(callable $decider, callable $delay = null): callable
public static function retry(callable $decider, ?callable $delay = null): callable
{
return static function (callable $handler) use ($decider, $delay): RetryMiddleware {
return new RetryMiddleware($decider, $handler, $delay);
@@ -187,12 +187,12 @@ final class Middleware
* Middleware that logs requests, responses, and errors using a message
* formatter.
*
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
*
* @param LoggerInterface $logger Logs messages.
* @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
* @param string $logLevel Level at which to log requests.
*
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
*
* @return callable Returns a function that accepts the next handler.
*/
public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable

View File

@@ -86,7 +86,7 @@ class Pool implements PromisorInterface
* @param ClientInterface $client Client used to send the requests
* @param array|\Iterator $requests Requests to send concurrently.
* @param array $options Passes through the options available in
* {@see \GuzzleHttp\Pool::__construct}
* {@see Pool::__construct}
*
* @return array Returns an array containing the response or an exception
* in the same order that the requests were sent.

View File

@@ -76,8 +76,8 @@ class PrepareBodyMiddleware
$expect = $options['expect'] ?? null;
// Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
if ($expect === false || $request->getProtocolVersion() < 1.1) {
// Return if disabled or using HTTP/1.0
if ($expect === false || $request->getProtocolVersion() === '1.0') {
return;
}

View File

@@ -61,7 +61,7 @@ final class RequestOptions
* Specifies whether or not cookies are used in a request or what cookie
* jar to use or what cookies to send. This option only works if your
* handler has the `cookie` middleware. Valid values are `false` and
* an instance of {@see \GuzzleHttp\Cookie\CookieJarInterface}.
* an instance of {@see Cookie\CookieJarInterface}.
*/
public const COOKIES = 'cookies';

View File

@@ -40,7 +40,7 @@ class RetryMiddleware
* and returns the number of
* milliseconds to delay.
*/
public function __construct(callable $decider, callable $nextHandler, callable $delay = null)
public function __construct(callable $decider, callable $nextHandler, ?callable $delay = null)
{
$this->decider = $decider;
$this->nextHandler = $nextHandler;
@@ -110,7 +110,7 @@ class RetryMiddleware
};
}
private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface
private function doRetry(RequestInterface $request, array $options, ?ResponseInterface $response = null): PromiseInterface
{
$options['delay'] = ($this->delay)(++$options['retries'], $response, $request);

View File

@@ -46,8 +46,8 @@ final class TransferStats
*/
public function __construct(
RequestInterface $request,
ResponseInterface $response = null,
float $transferTime = null,
?ResponseInterface $response = null,
?float $transferTime = null,
$handlerErrorData = null,
array $handlerStats = []
) {

View File

@@ -71,7 +71,7 @@ final class Utils
return \STDOUT;
}
return \GuzzleHttp\Psr7\Utils::tryFopen('php://output', 'w');
return Psr7\Utils::tryFopen('php://output', 'w');
}
/**
@@ -79,7 +79,7 @@ final class Utils
*
* The returned handler is not wrapped by any default middlewares.
*
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
* @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system.
*
* @throws \RuntimeException if no viable Handler is available.
*/
@@ -87,7 +87,7 @@ final class Utils
{
$handler = null;
if (\defined('CURLOPT_CUSTOMREQUEST')) {
if (\defined('CURLOPT_CUSTOMREQUEST') && \function_exists('curl_version') && version_compare(curl_version()['version'], '7.21.2') >= 0) {
if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) {
$handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
} elseif (\function_exists('curl_exec')) {

View File

@@ -50,7 +50,7 @@ function debug_resource($value = null)
*
* The returned handler is not wrapped by any default middlewares.
*
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
* @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system.
*
* @throws \RuntimeException if no viable Handler is available.
*

View File

@@ -1,6 +1,41 @@
# CHANGELOG
## 2.3.0 - 2025-08-22
### Added
- PHP 8.5 support
## 2.2.0 - 2025-03-27
### Fixed
- Revert "Allow an empty EachPromise to be resolved by running the queue"
## 2.1.0 - 2025-03-27
### Added
- Allow an empty EachPromise to be resolved by running the queue
## 2.0.4 - 2024-10-17
### Fixed
- Once settled, don't allow further rejection of additional promises
## 2.0.3 - 2024-07-18
### Changed
- PHP 8.4 support
## 2.0.2 - 2023-12-03
### Changed

View File

@@ -38,10 +38,10 @@ composer require guzzlehttp/promises
## Version Guidance
| Version | Status | PHP Version |
|---------|------------------------|--------------|
| 1.x | Bug and security fixes | >=5.5,<8.3 |
| 2.x | Latest | >=7.2.5,<8.4 |
| Version | Status | PHP Version |
|---------|---------------------|--------------|
| 1.x | Security fixes only | >=5.5,<8.3 |
| 2.x | Latest | >=7.2.5,<8.6 |
## Quick Start

View File

@@ -30,7 +30,7 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.36 || ^9.6.15"
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"autoload": {
"psr-4": {

View File

@@ -84,8 +84,8 @@ final class Coroutine implements PromiseInterface
}
public function then(
callable $onFulfilled = null,
callable $onRejected = null
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
return $this->result->then($onFulfilled, $onRejected);
}

View File

@@ -23,8 +23,8 @@ final class Each
*/
public static function of(
$iterable,
callable $onFulfilled = null,
callable $onRejected = null
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
@@ -46,8 +46,8 @@ final class Each
public static function ofLimit(
$iterable,
$concurrency,
callable $onFulfilled = null,
callable $onRejected = null
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
@@ -67,7 +67,7 @@ final class Each
public static function ofLimitAll(
$iterable,
$concurrency,
callable $onFulfilled = null
?callable $onFulfilled = null
): PromiseInterface {
return self::ofLimit(
$iterable,

View File

@@ -31,8 +31,8 @@ class FulfilledPromise implements PromiseInterface
}
public function then(
callable $onFulfilled = null,
callable $onRejected = null
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
// Return itself if there is no onFulfilled function.
if (!$onFulfilled) {

View File

@@ -25,16 +25,16 @@ class Promise implements PromiseInterface
* @param callable $cancelFn Fn that when invoked cancels the promise.
*/
public function __construct(
callable $waitFn = null,
callable $cancelFn = null
?callable $waitFn = null,
?callable $cancelFn = null
) {
$this->waitFn = $waitFn;
$this->cancelFn = $cancelFn;
}
public function then(
callable $onFulfilled = null,
callable $onRejected = null
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
if ($this->state === self::PENDING) {
$p = new Promise(null, [$this, 'cancel']);

View File

@@ -27,8 +27,8 @@ interface PromiseInterface
* @param callable $onRejected Invoked when the promise is rejected.
*/
public function then(
callable $onFulfilled = null,
callable $onRejected = null
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface;
/**

View File

@@ -31,8 +31,8 @@ class RejectedPromise implements PromiseInterface
}
public function then(
callable $onFulfilled = null,
callable $onRejected = null
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
// If there's no onRejected callback then just return self.
if (!$onRejected) {

View File

@@ -18,7 +18,7 @@ class RejectionException extends \RuntimeException
* @param mixed $reason Rejection reason.
* @param string|null $description Optional description.
*/
public function __construct($reason, string $description = null)
public function __construct($reason, ?string $description = null)
{
$this->reason = $reason;

View File

@@ -21,7 +21,7 @@ final class Utils
*
* @param TaskQueueInterface|null $assign Optionally specify a new queue instance.
*/
public static function queue(TaskQueueInterface $assign = null): TaskQueueInterface
public static function queue(?TaskQueueInterface $assign = null): TaskQueueInterface
{
static $queue;
@@ -144,7 +144,9 @@ final class Utils
$results[$idx] = $value;
},
function ($reason, $idx, Promise $aggregate): void {
$aggregate->reject($reason);
if (Is::pending($aggregate)) {
$aggregate->reject($reason);
}
}
)->then(function () use (&$results) {
ksort($results);

View File

@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 2.8.0 - 2025-08-23
### Added
- Allow empty lists as header values
### Changed
- PHP 8.5 support
## 2.7.1 - 2025-03-27
### Fixed
- Fixed uppercase IPv6 addresses in URI
### Changed
- Improve uploaded file error message
## 2.7.0 - 2024-07-18
### Added
- Add `Utils::redactUserInfo()` method
- Add ability to encode bools as ints in `Query::build`
## 2.6.3 - 2024-07-18
### Fixed
- Make `StreamWrapper::stream_stat()` return `false` if inner stream's size is `null`
### Changed
- PHP 8.4 support
## 2.6.2 - 2023-12-03
### Fixed

View File

@@ -24,8 +24,8 @@ composer require guzzlehttp/psr7
| Version | Status | PHP Version |
|---------|---------------------|--------------|
| 1.x | Security fixes only | >=5.4,<8.1 |
| 2.x | Latest | >=7.2.5,<8.4 |
| 1.x | EOL (2024-06-30) | >=5.4,<8.2 |
| 2.x | Latest | >=7.2.5,<8.6 |
## AppendStream
@@ -436,7 +436,7 @@ will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
## `GuzzleHttp\Psr7\Query::build`
`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string`
`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string`
Build a query string from an array of key value pairs.
@@ -498,11 +498,18 @@ a message.
## `GuzzleHttp\Psr7\Utils::readLine`
`public static function readLine(StreamInterface $stream, int $maxLength = null): string`
`public static function readLine(StreamInterface $stream, ?int $maxLength = null): string`
Read a line from the stream up to the maximum allowed buffer length.
## `GuzzleHttp\Psr7\Utils::redactUserInfo`
`public static function redactUserInfo(UriInterface $uri): UriInterface`
Redact the password in the user info part of a URI.
## `GuzzleHttp\Psr7\Utils::streamFor`
`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface`
@@ -674,7 +681,7 @@ termed a relative-path reference.
### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
`public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool`
Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
fragment component, identical to the base URI. When no base URI is given, only an empty URI reference

View File

@@ -61,8 +61,8 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.36 || ^9.6.15"
"http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"

Some files were not shown because too many files have changed in this diff Show More