""" Maakt HTML uit de handleiding-editor veilig: enkel een vaste lijst tags en attributen blijft over. Scripts, event-handlers (onclick, ...), stijlen en javascript:-links verdwijnen. """ from html import escape from html.parser import HTMLParser TOEGELATEN = { 'p': (), 'br': (), 'hr': (), 'div': (), 'span': (), 'h2': ('id',), 'h3': ('id',), 'h4': ('id',), 'strong': (), 'b': (), 'em': (), 'i': (), 'u': (), 's': (), 'mark': (), 'kbd': (), 'sub': (), 'sup': (), 'ul': (), 'ol': ('start',), 'li': (), 'blockquote': (), 'code': (), 'pre': (), 'a': ('href', 'title', 'target'), 'img': ('src', 'alt', 'title', 'width'), 'table': (), 'thead': (), 'tbody': (), 'tr': (), 'th': ('colspan', 'rowspan'), 'td': ('colspan', 'rowspan'), 'figure': (), 'figcaption': (), } LEEG = {'br', 'hr', 'img'} KLASSEN = {'tip', 'let-op', 'info'} # kaders in de editor WEGGOOIEN_MET_INHOUD = {'script', 'style', 'iframe', 'object', 'embed', 'noscript', 'template', 'svg', 'math'} # Word/Docs plakt deze tags; de inhoud blijft, de tag niet VERTALING = {'b': 'strong', 'i': 'em', 'h1': 'h2', 'h5': 'h4', 'h6': 'h4'} def _veilige_url(url, afbeelding=False): u = (url or '').strip() laag = u.lower().replace('\x00', '') if afbeelding and laag.startswith('data:image/') and ';base64,' in laag and not laag.startswith('data:image/svg'): return u if laag.startswith(('http://', 'https://', '/', '#')) or (not afbeelding and laag.startswith('mailto:')): return u return None class _Schoonmaker(HTMLParser): def __init__(self): super().__init__(convert_charrefs=True) self.uit = [] self.open = [] self.weg = 0 def handle_starttag(self, tag, attrs): if tag in WEGGOOIEN_MET_INHOUD: if tag not in LEEG: self.weg += 1 return if self.weg: return tag = VERTALING.get(tag, tag) if tag not in TOEGELATEN: return delen = [tag] for naam, waarde in attrs: naam = (naam or '').lower() if naam == 'class': klassen = [k for k in (waarde or '').split() if k in KLASSEN] if klassen: delen.append(f'class="{escape(" ".join(klassen))}"') continue if naam not in TOEGELATEN[tag]: continue if naam in ('href', 'src'): waarde = _veilige_url(waarde, afbeelding=(naam == 'src')) if waarde is None: continue if naam == 'target': waarde = '_blank' if naam in ('width', 'colspan', 'rowspan', 'start') and not str(waarde or '').isdigit(): continue delen.append(f'{naam}="{escape(waarde or "", quote=True)}"') if tag == 'a' and any(d.startswith('target=') for d in delen): delen.append('rel="noopener noreferrer"') if tag == 'img' and not any(d.startswith('src=') for d in delen): return self.uit.append('<' + ' '.join(delen) + '>') if tag not in LEEG: self.open.append(tag) def handle_startendtag(self, tag, attrs): self.handle_starttag(tag, attrs) tag = VERTALING.get(tag, tag) if tag not in LEEG and self.open and self.open[-1] == tag: self.handle_endtag(tag) def handle_endtag(self, tag): if tag in WEGGOOIEN_MET_INHOUD: self.weg = max(0, self.weg - 1) return if self.weg: return tag = VERTALING.get(tag, tag) if tag not in TOEGELATEN or tag in LEEG or tag not in self.open: return while self.open: t = self.open.pop() self.uit.append(f'') if t == tag: break def handle_data(self, data): if not self.weg: self.uit.append(escape(data, quote=False)) def resultaat(self): while self.open: self.uit.append(f'') return ''.join(self.uit) def schoon(html): p = _Schoonmaker() p.feed(html or '') p.close() return p.resultaat().strip()