diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ae2394e42..5e10d20c9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ HumHub Changelog - Fix #7248: Upgrade jQuery Highlight plugin - Fix #7254: Fix Login view HTML element ID from `user-auth-login-modal` to `user-auth-login` - Fix #7250: Check writable path +- Enh #7253: CSV/XLSX export improvements 1.16.3 (Unreleased) -------------------------- diff --git a/protected/humhub/components/export/SpreadsheetExport.php b/protected/humhub/components/export/SpreadsheetExport.php index 762f3fb5ee..dda0ced138 100644 --- a/protected/humhub/components/export/SpreadsheetExport.php +++ b/protected/humhub/components/export/SpreadsheetExport.php @@ -7,6 +7,7 @@ namespace humhub\components\export; +use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; use Yii; use yii\base\Component; @@ -14,6 +15,7 @@ use yii\base\InvalidConfigException; use yii\data\ActiveDataProvider; use yii\data\BaseDataProvider; use yii\di\Instance; +use yii\helpers\ArrayHelper; use yii\i18n\Formatter; /** @@ -276,10 +278,10 @@ class SpreadsheetExport extends Component } /** - * Composes header row contents. - * @param Spreadsheet $spreadsheet - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ + * Composes header row contents. + * @param Spreadsheet $spreadsheet + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ protected function composeHeaderRow($spreadsheet) { $worksheet = $spreadsheet->getActiveSheet(); @@ -346,6 +348,7 @@ class SpreadsheetExport extends Component foreach ($this->columns as $columnIndex => $column) { $coordinate = $this->getColumnLetter($columnIndex + 1) . $row; $value = $column->renderDataCellContent($model, $key, $index); + $value = $this->sanitizeValue($value); if ($column->dataType !== null) { $worksheet->getCell($coordinate)->setValueExplicit($value, $column->dataType); @@ -359,6 +362,34 @@ class SpreadsheetExport extends Component } } + /** + * Sanitize value to prevent injection. + */ + private function sanitizeValue(?string $value): ?string + { + if ( + empty($value) || + !in_array( + ucfirst(ArrayHelper::getValue($this->resultConfig, 'writerType', (new ExportResult())->writerType)), + [IOFactory::WRITER_CSV, IOFactory::WRITER_XLSX, IOFactory::WRITER_XLS], + ) + ) { + return $value; + } + + // Check for risky starting characters or formula-like values and prepend single quote + if (strpbrk($value[0], '=+-@,;' . "\t" . "\r") !== false || preg_match('/^\d+[+\-*\/].+/', $value)) { + $value = "'" . $value; + } + + // Sanitize escaping quotes, wrapping in double quotes if needed + if (strpbrk($value, "\"\n,") !== false) { + $value = '"' . str_replace('"', '""', $value) . '"'; + } + + return $value; + } + /** * Enable AutoSize for Export * @param Spreadsheet $spreadsheet