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
29 lines
1.2 KiB
TypeScript
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)
|
|
}
|
|
}
|