862243a2b8
Ревью раунда 2: F-CSV шире — формула может уйти не только через DealExport, но
и через центральные писатели ОТЧЁТОВ (Managers/Sources/Billing/Deals × csv/xlsx).
Прежний фикс нейтрализовал в DealsExportProvider — неверный слой: он кормит и
JSON-формат, где апостроф портил данные (ReportJobControllerTest).
Перенос защиты в писатели:
- CsvFormatter — CsvFormulaGuard::neutralizeCell (апостроф, числа не трогаются)
- XlsxFormatter — опасные строки через setCellValueExplicit TYPE_STRING
(Excel НЕ вычисляет формулу; XLSX опаснее — считает без предупреждения)
- DealsExportProvider — откат к сырым данным (JSON больше не портится)
CsvFormulaGuard: + isDangerous()/neutralizeCell() — numeric-aware (ведущий «-»
числа не экранируется).
TDD: unit-тесты CsvFormatter + XlsxFormatter (load xlsx → datatype 's', не 'f').
DealExportController (OpenSpout, отдельный путь Сделок) — без изменений.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
32 lines
1.3 KiB
PHP
32 lines
1.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Reports\Formatters\CsvFormatter;
|
|
|
|
// F-CSV (wide): центральный писатель отчётов должен нейтрализовать formula-
|
|
// инъекцию во всех типах отчётов (Managers/Sources/Billing/Deals), не только
|
|
// в DealsExportProvider. Числовые ячейки (ведущий «-» = минус) не трогаются.
|
|
|
|
it('нейтрализует формулы в ячейках, числа не трогает', function () {
|
|
$out = (new CsvFormatter)->format(
|
|
['Контакт', 'Сумма'],
|
|
[
|
|
['=HYPERLINK("http://evil")', '-100.00'],
|
|
['@SUM(1+1)', 1500],
|
|
['+вредная', '0.00'],
|
|
],
|
|
);
|
|
|
|
// формулы нейтрализованы апострофом
|
|
expect($out)->toContain("'=HYPERLINK(");
|
|
expect($out)->toContain("'@SUM(1+1)");
|
|
expect($out)->toContain("'+вредная");
|
|
// до фикса ячейка обрамлялась бы как "=HYPERLINK( без апострофа
|
|
expect($out)->not->toContain('"=HYPERLINK(');
|
|
// числа НЕ тронуты (ведущий минус — легитимный знак)
|
|
expect($out)->toContain('-100.00');
|
|
expect($out)->not->toContain("'-100.00");
|
|
expect($out)->toContain('1500');
|
|
});
|