diff --git a/core/csvbulkexport.class.inc.php b/core/csvbulkexport.class.inc.php index bc0d957ac..0e4ded346 100644 --- a/core/csvbulkexport.class.inc.php +++ b/core/csvbulkexport.class.inc.php @@ -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( <<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) diff --git a/core/excelbulkexport.class.inc.php b/core/excelbulkexport.class.inc.php index 6f4632962..8cf9be44e 100644 --- a/core/excelbulkexport.class.inc.php +++ b/core/excelbulkexport.class.inc.php @@ -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( <<aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) { + return ExportHelper::SanitizeField($sRet, ''); + } + return $sRet; } diff --git a/css/backoffice/application/bulk/_all.scss b/css/backoffice/application/bulk/_all.scss index 495ed376f..fcd615d40 100644 --- a/css/backoffice/application/bulk/_all.scss +++ b/css/backoffice/application/bulk/_all.scss @@ -4,3 +4,4 @@ */ @import "bulk-modify"; +@import "bulk-export"; diff --git a/css/backoffice/application/bulk/_bulk-export.scss b/css/backoffice/application/bulk/_bulk-export.scss new file mode 100644 index 000000000..b595b6c82 --- /dev/null +++ b/css/backoffice/application/bulk/_bulk-export.scss @@ -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; + } +} \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig index c7a528f08..7179d960e 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig @@ -11,7 +11,7 @@
- +

{{ 'ExcelExport:PreparingExport'|dict_s }}

'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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/da.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/da.dictionary.itop.bulk.php index 19f984f7d..8ccf87230 100644 --- a/dictionaries/ui/application/bulk/da.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/da.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/de.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/de.dictionary.itop.bulk.php index 2aa593d27..08d2ee358 100644 --- a/dictionaries/ui/application/bulk/de.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/de.dictionary.itop.bulk.php @@ -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. Erfahren Sie mehr in unserer Dokumentation.', + '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. Erfahren Sie mehr in unserer Dokumentation.', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/en.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/en.dictionary.itop.bulk.php index a2d8e73f4..909f79926 100644 --- a/dictionaries/ui/application/bulk/en.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/en.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.', + '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. Learn more in our documentation.', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.', + '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', ]); diff --git a/dictionaries/ui/application/bulk/en_gb.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/en_gb.dictionary.itop.bulk.php index 442855c19..e5f910ffe 100644 --- a/dictionaries/ui/application/bulk/en_gb.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/en_gb.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.', + '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. Learn more in our documentation.', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.', + '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', ]); diff --git a/dictionaries/ui/application/bulk/es_cr.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/es_cr.dictionary.itop.bulk.php index 732fca221..64d08840f 100644 --- a/dictionaries/ui/application/bulk/es_cr.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/es_cr.dictionary.itop.bulk.php @@ -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. Obtenga más información en nuestra documentación.', + '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. Obtenga más información en nuestra documentación.', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/fr.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/fr.dictionary.itop.bulk.php index fe822187d..b35019482 100644 --- a/dictionaries/ui/application/bulk/fr.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/fr.dictionary.itop.bulk.php @@ -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é. Pour en savoir plus, consultez notre documentation.', + '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é. Pour en savoir plus, consultez notre documentation.', + '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. Pour en savoir plus, consultez notre documentation', + '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é', ]); diff --git a/dictionaries/ui/application/bulk/hu.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/hu.dictionary.itop.bulk.php index ca88b25d9..478a85e1b 100644 --- a/dictionaries/ui/application/bulk/hu.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/hu.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/it.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/it.dictionary.itop.bulk.php index 9a2aaf3a6..185c9e7be 100644 --- a/dictionaries/ui/application/bulk/it.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/it.dictionary.itop.bulk.php @@ -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. Ulteriori informazioni nella nostra documentazione.', + '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. Ulteriori informazioni nella nostra documentazione.', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/ja.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/ja.dictionary.itop.bulk.php index 2c6029cd0..cc5bf0163 100644 --- a/dictionaries/ui/application/bulk/ja.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/ja.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/nl.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/nl.dictionary.itop.bulk.php index 34caf4cf9..f47b0ca6c 100644 --- a/dictionaries/ui/application/bulk/nl.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/nl.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/pl.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/pl.dictionary.itop.bulk.php index ae71e7ee1..c7cee27e9 100644 --- a/dictionaries/ui/application/bulk/pl.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/pl.dictionary.itop.bulk.php @@ -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. Dowiedz się więcej w naszej dokumentacji.', + '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. Dowiedz się więcej w naszej dokumentacji.', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/pt_br.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/pt_br.dictionary.itop.bulk.php index b7347bd71..b79f59c64 100644 --- a/dictionaries/ui/application/bulk/pt_br.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/pt_br.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/ru.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/ru.dictionary.itop.bulk.php index 11e539a25..8b3de319c 100644 --- a/dictionaries/ui/application/bulk/ru.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/ru.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/sk.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/sk.dictionary.itop.bulk.php index e3f6792d9..d87bfa6a7 100644 --- a/dictionaries/ui/application/bulk/sk.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/sk.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/tr.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/tr.dictionary.itop.bulk.php index 9c93546d0..a7376494c 100644 --- a/dictionaries/ui/application/bulk/tr.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/tr.dictionary.itop.bulk.php @@ -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. Learn more in our documentation.~~', + '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. Learn more in our documentation.~~', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/dictionaries/ui/application/bulk/zh_cn.dictionary.itop.bulk.php b/dictionaries/ui/application/bulk/zh_cn.dictionary.itop.bulk.php index 08a354d54..9cab4e886 100644 --- a/dictionaries/ui/application/bulk/zh_cn.dictionary.itop.bulk.php +++ b/dictionaries/ui/application/bulk/zh_cn.dictionary.itop.bulk.php @@ -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 设置能够安全的处理该文件. 进入我们的文档了解更多.', + 'UI:Bulk:Export:MaliciousInjection:Alert:Message' => '在 Microsoft Excel 中打开不信任的文件可能导致公式注入. 请确保 Excel 设置能够安全的处理该文件. 进入我们的文档了解更多.', + 'UI:Bulk:Export:MaliciousInjection:Sanitization:Alert:Message' => 'Some values have been sanitized to prevent potential security issues in Microsoft Excel. Learn more in our documentation.~~', + '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~~', ]); diff --git a/js/utils.js b/js/utils.js index 8bbda0ccf..c7225e23b 100644 --- a/js/utils.js +++ b/js/utils.js @@ -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'); diff --git a/sources/Application/Helper/ExportHelper.php b/sources/Application/Helper/ExportHelper.php index 5e76b9d7e..8ccc3b189 100644 --- a/sources/Application/Helper/ExportHelper.php +++ b/sources/Application/Helper/ExportHelper.php @@ -3,6 +3,9 @@ namespace Combodo\iTop\Application\Helper; use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\Input\InputWithLabel; +use Combodo\iTop\Application\UI\Base\UIBlock; use Dict; use utils; @@ -17,12 +20,91 @@ use utils; */ class ExportHelper { + /** + * @since 3.2.3 3.3.0 N°6977 + */ + public const EXCEL_FORMULA_CHARACTERS = ['=', '=', '+', '+', '-', '-', '@', '@', '|', "\t", "\n", "\r"]; + public static function GetAlertForExcelMaliciousInjection() { $sWikiUrl = 'https://www.itophub.io/wiki/page?id='.utils::GetItopVersionWikiSyntax().'%3Auser%3Alists#excel_export'; - $oAlert = AlertUIBlockFactory::MakeForWarning(Dict::S('UI:Bulk:Export:MaliciousInjection:Alert:Title'), Dict::Format('UI:Bulk:Export:MaliciousInjection:Alert:Message', $sWikiUrl), 'ibo-excel-malicious-injection-alert'); + $oAlert = AlertUIBlockFactory::MakeForWarning(Dict::S('UI:Bulk:Export:MaliciousInjection:Alert:Title'), Dict::Format('UI:Bulk:Export:MaliciousInjection:Alert:Message', $sWikiUrl), 'ibo-sanitize-excel-export--alert'); $oAlert->EnableSaveCollapsibleState(true) ->SetIsClosable(false); return $oAlert; } + + /** + * @return InputWithLabel + * + * @since 3.2.3 + */ + public static function GetInputForSanitizeExcelExport(): UIBlock + { + + $oSanitizeInput = InputUIBlockFactory::MakeForInputWithLabel(Dict::S('UI:Bulk:Export:MaliciousInjection:Input:Label'), 'sanitize_excel_export', 1, 'ibo-sanitize-excel-export--input', 'checkbox'); + $oSanitizeInput->GetInput()->SetIsChecked(true); + $oSanitizeInput->SetBeforeInput(false); + $oSanitizeInput->SetDescription(Dict::S('UI:Bulk:Export:MaliciousInjection:Input:Tooltip')); + $oSanitizeInput->GetInput()->AddCSSClass('ibo-input-checkbox'); + + return $oSanitizeInput; + } + + /** + * @param string $sValue + * @return bool + * + * @since 3.2.3 + */ + public static function IsValueFormulaCandidate(string $sValue): bool + { + // An empty value cannot be a formula, so we can skip all the checks in this case + if ($sValue === '') { + return false; + } + + $bHasFormulaCandidate = false; + $sFirstChar = mb_substr($sValue, 0, 1); + $bHasFormulaCandidate |= in_array($sFirstChar, static::EXCEL_FORMULA_CHARACTERS, true); + + // If the string is less than 3 characters long, it cannot start with a url encoded formula character, so we can skip this check in this case + if (mb_strlen($sValue) < 3) { + return $bHasFormulaCandidate; + } + + // Additionally, check if the first three character could be a formula character url encoded + $sFirstThreeChars = mb_strtoupper(mb_substr($sValue, 0, 3)); + + $aUrlEncodedFormulaCharacters = array_map('urlencode', static::EXCEL_FORMULA_CHARACTERS); + $bHasFormulaCandidate |= in_array($sFirstThreeChars, $aUrlEncodedFormulaCharacters, true); + + return $bHasFormulaCandidate; + } + + /** + * @param string $sField + * @param string $sTextQualifier + * @return string + * + * @since 3.2.3 + */ + public static function SanitizeField(string $sField, string $sTextQualifier = ''): string + { + if ($sField === '') { + return $sField; + } + + $sQualifier = $sTextQualifier; + if ($sQualifier !== '' && str_starts_with($sField, $sQualifier)) { + $sAfterQualifier = substr($sField, strlen($sQualifier)); + if (self::IsValueFormulaCandidate($sAfterQualifier)) { + return $sQualifier."'".$sAfterQualifier; + } + } elseif (self::IsValueFormulaCandidate($sField)) { + return "'".$sField; + } + + return $sField; + } } diff --git a/tests/php-unit-tests/unitary-tests/core/BulkExportTest.php b/tests/php-unit-tests/unitary-tests/core/BulkExportTest.php index 1fbe91ae7..bcdf9b541 100644 --- a/tests/php-unit-tests/unitary-tests/core/BulkExportTest.php +++ b/tests/php-unit-tests/unitary-tests/core/BulkExportTest.php @@ -158,4 +158,173 @@ EOF; $this->assertEquals($sExpectedValue, $data); } + /** + * @dataProvider OrganizationsForExportSanitizeExcelExportProvider + * + * @param $aListOrg + * @param $aExpectedValues + * @return void + * @throws \CoreCannotSaveObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \OQLException + * @throws \ReflectionException + */ + public function testExportWithSanitizeExcelExport( + $aListOrg, + $aExpectedValues, + ) { + // Create tests organizations to have enough data + $iFirstOrg = 0; + foreach ($aListOrg as $aOrg) { + $oObj = $this->CreateOrganization($aOrg[0]); + if ($aOrg[1] === false) { + $oObj->Set('status', 'inactive'); + $oObj->DBUpdate(); + } + if ($iFirstOrg === 0) { + $iFirstOrg = $oObj->GetKey(); + } + } + + $aStatusInfo = [ + "fields" => [ + [ + "sFieldSpec" => "name", + "sAlias" => "Organization", + "sClass" => "Organization", + "sAttCode" => "name", + "sLabel" => "Name", + "sColLabel" => "Name", + ], + ], + "text_qualifier" => "\"", + "charset" => "UTF-8", + "separator" => ",", + "date_format" => "Y-m-d H:i:s", + "formatted_text" => false, + "show_obsolete_data" => false, + 'ignore_excel_sanitization' => false, + ]; + $sStatus = []; + $oSearch = DBObjectSearch::FromOQL('SELECT Organization'); + $oExporter = BulkExport::FindExporter('csv', $oSearch); + $oExporter->SetStatusInfo($aStatusInfo); + $oExporter->SetObjectList($oSearch); + $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); + + $data = $oExporter->GetHeader(); + $data .= $oExporter->GetNextChunk($sStatus); + + // Check that the value is sanitized as expected (with a ' prefix) + foreach ($aExpectedValues as $sExpectedValue) { + $this->assertStringContainsString($sExpectedValue, $data, "The value $sExpectedValue is expected to be found in the export result"); + } + } + + /** + * @dataProvider OrganizationsForExportSanitizeExcelExportProvider + * + * @param $aListOrg + * @param $aExpectedValues + * @return void + * @throws \CoreCannotSaveObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \OQLException + * @throws \ReflectionException + */ + public function testExportWithoutSanitizeExcelExport( + $aListOrg, + $aExpectedValues, + ) { + // Create tests organizations to have enough data + $iFirstOrg = 0; + foreach ($aListOrg as $aOrg) { + $oObj = $this->CreateOrganization($aOrg[0]); + if ($aOrg[1] === false) { + $oObj->Set('status', 'inactive'); + $oObj->DBUpdate(); + } + if ($iFirstOrg === 0) { + $iFirstOrg = $oObj->GetKey(); + } + } + + $aStatusInfo = [ + "fields" => [ + [ + "sFieldSpec" => "name", + "sAlias" => "Organization", + "sClass" => "Organization", + "sAttCode" => "name", + "sLabel" => "Name", + "sColLabel" => "Name", + ], + ], + "text_qualifier" => "\"", + "charset" => "UTF-8", + "separator" => ",", + "date_format" => "Y-m-d H:i:s", + "formatted_text" => false, + "show_obsolete_data" => false, + 'ignore_excel_sanitization' => true, + ]; + $sStatus = []; + $oSearch = DBObjectSearch::FromOQL('SELECT Organization'); + $oExporter = BulkExport::FindExporter('csv', $oSearch); + $oExporter->SetStatusInfo($aStatusInfo); + $oExporter->SetObjectList($oSearch); + $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); + + $data = $oExporter->GetHeader(); + $data .= $oExporter->GetNextChunk($sStatus); + + // Check that the value is not sanitized + foreach ($aListOrg as $sExpectedValue) { + $this->assertStringContainsString($sExpectedValue[0], $data, "The value $sExpectedValue[0] is expected to be found in the export result"); + } + } + + public function OrganizationsForExportSanitizeExcelExportProvider() + { + return [ + 'Page1' => [ + 'list_org' => [ + ['=org1', true], + ['+org2', true], + ['-org3', true], + ['@org4', true], + ["\t=org5", true], + ["\rorg6", true], + ["\r\t\r =org7", true], + ['=org8', true], + ['+org9', true], + ['-org10', true], + ['@org11', true], + ['|org12', true], + ['%3Dorg13', true], + ['%3dorg14', true], + ['org15', true], + ], + 'export_org' => [ + "'=org1", + "'+org2", + "'-org3", + "'@org4", + "'\t=org5", + "'\rorg6", + "'\r\t\r =org7", + "'=org8", + "'+org9", + "'-org10", + "'@org11", + "'|org12", + "'%3Dorg13", + "'%3dorg14", + "org15", + ], + ], + ]; + } }