From 18d37d1e0190fa690a177860730abd77864db4d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 10:03:24 +0000 Subject: [PATCH] Runner: Analyse null erlaubt, Adresse optional (R10) Co-Authored-By: Claude Sonnet 5.5 --- runner/src/analytics-inject.js | 3 ++- runner/src/templates/datenschutz-analyse.html | 2 +- runner/src/validate.js | 3 ++- runner/test/analytics-inject.test.js | 16 +++++++++++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/runner/src/analytics-inject.js b/runner/src/analytics-inject.js index d1a41ab..bc92d3d 100644 --- a/runner/src/analytics-inject.js +++ b/runner/src/analytics-inject.js @@ -52,7 +52,8 @@ export async function injectAnalytics(dir, analytics, { baseUrl, onLine, fs = no const tag = scriptTag(analytics, baseUrl); const p = analytics.privacy || {}; const template = await fs.readFile(TEMPLATE_URL, 'utf8'); - const page = template.replace(/\{\{(company|address|email)\}\}/g, (_, k) => esc(p[k] ?? '')); + const withAddr = p.address ? template.replace(/\{\{#address\}\}|\{\{\/address\}\}/g, '') : template.replace(/\{\{#address\}\}[\s\S]*?\{\{\/address\}\}/g, ''); + const page = withAddr.replace(/\{\{(company|address|email)\}\}/g, (_, k) => esc(p[k] ?? '')); await fs.writeFile(join(dir, PAGE), page, 'utf8'); await fs.chmod(join(dir, PAGE), 0o644); let n = 0; diff --git a/runner/src/templates/datenschutz-analyse.html b/runner/src/templates/datenschutz-analyse.html index 448312a..675b112 100644 --- a/runner/src/templates/datenschutz-analyse.html +++ b/runner/src/templates/datenschutz-analyse.html @@ -17,7 +17,7 @@

Vorläufiger Entwurf: Dieser Text wird vor der Veröffentlichung noch redaktionell und rechtlich überarbeitet.

Verantwortlicher

-

{{company}}
{{address}}
E-Mail: {{email}}

+

{{company}}
{{#address}}{{address}}
{{/address}}E-Mail: {{email}}

Zweck

Wir möchten verstehen, wie diese Website genutzt wird, um sie zu verbessern (zum Beispiel welche Seiten gelesen werden und wo Besucher hängen bleiben). Es gibt keine Werbung und kein Profiling.

diff --git a/runner/src/validate.js b/runner/src/validate.js index 15671d0..a64a08e 100644 --- a/runner/src/validate.js +++ b/runner/src/validate.js @@ -48,7 +48,7 @@ export function validateJob(body) { } } let analytics; - if (body.analytics !== undefined) { + if (body.analytics !== undefined && body.analytics !== null) { const a = body.analytics; if (!a || typeof a !== 'object' || Array.isArray(a)) return { ok: false, error: 'Ungültige Analyse-Angaben' }; if (typeof a.siteKey !== 'string' || !RE_SITE_KEY.test(a.siteKey)) return { ok: false, error: 'Ungültiger Analyse-Schlüssel' }; @@ -59,6 +59,7 @@ export function validateJob(body) { for (const [k, max] of [['company', 200], ['address', 300], ['email', 200]]) { const v = pr[k]; // eslint-disable-next-line no-control-regex + if (k === 'address' && (v === undefined || v === '')) { privacy[k] = ''; continue; } if (typeof v !== 'string' || !v.trim() || v.length > max || /[\x00-\x1f\x7f]/.test(v)) return { ok: false, error: `Ungültige Analyse-Datenschutzangabe: ${k}` }; privacy[k] = v; } diff --git a/runner/test/analytics-inject.test.js b/runner/test/analytics-inject.test.js index 04bb280..284eb88 100644 --- a/runner/test/analytics-inject.test.js +++ b/runner/test/analytics-inject.test.js @@ -107,11 +107,17 @@ test('validateJob: analytics streng, Weiterreichen in den Job', () => { assert.equal(ok.ok, true); assert.deepEqual(ok.job.analytics, A()); assert.equal(validateJob(base).job.analytics, undefined); + const nul = validateJob({ ...base, analytics: null }); + assert.equal(nul.ok, true); assert.equal(nul.job.analytics, undefined); + const noAddr = validateJob({ ...base, analytics: A({ privacy: { company: 'F', email: 'e@x.de' } }) }); + assert.equal(noAddr.ok, true); assert.equal(noAddr.job.analytics.privacy.address, ''); + assert.equal(validateJob({ ...base, analytics: A({ privacy: { company: 'F', email: '' } }) }).ok, false); + assert.equal(validateJob({ ...base, analytics: A({ privacy: { email: 'e@x.de' } }) }).ok, false); const bad = [ A({ siteKey: 'kurz' }), A({ siteKey: 'A'.repeat(20) }), A({ color: '#ABCDEF' }), A({ color: '#abc' }), A({ privacy: undefined }), A({ privacy: { ...A().privacy, company: 'x'.repeat(201) } }), A({ privacy: { ...A().privacy, address: 'x'.repeat(301) } }), A({ privacy: { ...A().privacy, email: 'x'.repeat(201) } }), - A({ privacy: { ...A().privacy, company: 'a\nb' } }), A({ privacy: { ...A().privacy, email: 5 } }), 'x', [], null, + A({ privacy: { ...A().privacy, company: 'a\nb' } }), A({ privacy: { ...A().privacy, email: 5 } }), 'x', [], ]; for (const a of bad) { const r = validateJob({ ...base, analytics: a }); assert.equal(r.ok, false, JSON.stringify(a)); assert.match(r.error, /Analyse/); } }); @@ -171,3 +177,11 @@ test('Dev-Modus (prepareWorkspace) fasst Dateien nicht an', async () => { assert.equal(get(workDir, 'index.html'), 'NEU'); assert.ok(!existsSync(join(workDir, 'datenschutz-analyse.html'))); }); + +test('leere Adresse: keine leere Zeile, kein Platzhalterrest', async () => { + const d = dir(); put(d, 'index.html', ''); + await injectAnalytics(d, A({ privacy: { company: 'Muster GmbH', address: '', email: 'info@muster.de' } }), { baseUrl: BASE }); + const p = get(d, 'datenschutz-analyse.html'); + assert.ok(p.includes('Muster GmbH
E-Mail:')); + assert.ok(!p.includes('{{') && !p.includes('

')); +});