Files
Jakub Zych 5beb1c5634 fix: sanitize journal HTML instead of blanking it on a regex match
The three-regex gate could be bypassed (an attribute without a space,
<svg/onload>, entity-encoded or tab-split javascript: URLs, MathML
xlink:href, base, meta refresh, forms) and it blanked a whole post or
category when ordinary text such as "Data: 12 maja 2024" matched.

- sanitizeJournalHtml runs sanitize-html with the Journal plugin's
  bluemonday policy: UGC elements plus footer, class, schema.org
  microdata, four validated inline styles, footnote roles, dir=auto,
  two chat transcript data attributes; http, https, mailto and
  relative URLs only
- gateJournalHtml and isSafeJournalHtml keep their signatures; html is
  always the sanitized value and is never blanked
- post body, category content and both page payloads are sanitized;
  the blocked notice, its style and string, and the blocked props and
  content_blocked field are removed
- toPostView moves to postView.ts so list pages and cards do not load
  the sanitizer
- tests cover every bypass from the review, the Data:/metadata: text
  and an imported author footer; the SSR smoke expects the unsafe
  sample's safe paragraphs
2026-10-10 00:01:01 +02:00

29 lines
1.2 KiB
TypeScript

// View shaping for SSR payloads. Pages run API rows through these in
// useAsyncData's transform, so the serialized payload carries only what the
// page renders: no raw `content` source and no `translations`. The post
// page's view, which sanitizes the body, is in postView.ts, so list pages and
// cards that import this file do not load the sanitizer.
import type { JournalPost } from './types.ts'
/** A post row as a list or card renders it: without body, source or translations. */
export function toListPost(post: JournalPost): JournalPost {
return { ...post, content: null, content_html: null, translations: null }
}
/**
* Formats an API timestamp as a long date for `language` (a BCP 47 tag such
* as "pl-PL"). UTC on both server and browser, so hydration matches. Returns
* "" for a missing or invalid value.
*/
export function formatJournalDate(iso: string | null | undefined, language: string): string {
if (!iso) return ''
const d = new Date(iso)
if (Number.isNaN(d.getTime())) return ''
try {
return new Intl.DateTimeFormat(language || 'en', { dateStyle: 'long', timeZone: 'UTC' }).format(d)
} catch {
return new Intl.DateTimeFormat('en', { dateStyle: 'long', timeZone: 'UTC' }).format(d)
}
}