UPG-100 batch selection CSV: also neutralise a leading TAB or CR (OWASP's full list of formula triggers), asserted by the gate

This commit is contained in:
Doxa Maps 2026-09-30 21:04:51 +00:00
parent 03af1d87b5
commit ee955bac5a
2 changed files with 7 additions and 3 deletions

View file

@ -45,8 +45,9 @@ const records = [
{ slug: 'q-comma', name: 'Bhil, Tadvi "Hill"', country_code: 'IND', population: 3100 },
{ slug: 'q-formula', name: '=HYPERLINK("x")', country_code: 'NPL', population: 450 },
{ slug: 'q-newline', name: 'Line one\nLine two', country_code: 'NPL', population: 7 },
{ slug: 'q-tab', name: '\t=cmd', country_code: 'NPL', population: 1 },
]
const from = new Map([['agaria', ['Mahārāshtra', 'Goa']], ['q-comma', ['Goa']], ['q-formula', ['Nepal']], ['q-newline', ['Nepal']]])
const from = new Map([['agaria', ['Mahārāshtra', 'Goa']], ['q-comma', ['Goa']], ['q-formula', ['Nepal']], ['q-newline', ['Nepal']], ['q-tab', ['Nepal']]])
const color = '#e69f00'
const csv = X.selectionCsv(records, from, { color })
const text = X.selectionClipboard(selection, { color }, csv)
@ -86,6 +87,8 @@ ok(col(rows[4], 'name') === 'Line one\nLine two', `a name with a line break read
'commas, quotes and line breaks survive a real CSV parse')
ok(col(rows[3], 'name') === '\'=HYPERLINK("x")', `a leading = reads back as ${JSON.stringify(col(rows[3], 'name'))}: a spreadsheet would run it`,
'a leading = + - @ is neutralised')
ok(col(rows[5], 'name') === "'\t=cmd", `a leading TAB reads back as ${JSON.stringify(col(rows[5], 'name'))}: OWASP lists it as a formula trigger`,
'a leading TAB or CR is neutralised too (OWASP)')
ok(col(rows[1], 'territories') === 'Mahārāshtra; Goa' && col(rows[1], 'color') === color && col(rows[1], 'slug') === 'agaria',
`row 1 is ${JSON.stringify(rows[1])}`, 'each row carries its slug, the territories that contain it, and the colour')

View file

@ -13,14 +13,15 @@
//
// THE CSV: one row per people group, header first, CRLF line ends. The cells follow the library's rules
// (useCsvExport's esc): quote a cell holding , " CR or LF and double its quotes; prefix a leading = + - @
// with a single quote, so a spreadsheet never runs a name as a formula.
// with a single quote, so a spreadsheet never runs a name as a formula. Also a leading TAB or CR (OWASP's
// full list of formula triggers; the library's esc stops at = + - @).
import { formatClipboard } from './territory-counts.js';
export const SELECTION_CSV_COLUMNS = ['slug', 'name', 'country_code', 'population', 'territories', 'color'];
export function csvCell(v) {
let s = v === null || v === undefined ? '' : String(v);
if (/^[=+\-@]/.test(s)) s = "'" + s;
if (/^[=+\-@\t\r]/.test(s)) s = "'" + s;
if (/[",\n\r]/.test(s)) s = '"' + s.replace(/"/g, '""') + '"';
return s;
}