"""Rewrite root-relative links only in the HTTP preview response. The published ZIP / immutable build files are never modified. Source element markup is preserved so that e.g. canonical links and SEO headers are not rewritten or reformatted by an HTML serializer. """ import re from html.parser import HTMLParser _ATTR = re.compile(r'''(?P\b(?:href|src|action|poster))(?P\s*=\s*)(?P["'])(?P.*?)(?P=q)''', re.I | re.S) class _PreviewHTMLParser(HTMLParser): def __init__(self, prefix): super().__init__(convert_charrefs=False) self.prefix=prefix self.out=[] def _start(self): raw=self.get_starttag_text() def replace(match): url=match.group('value') if url.startswith('/') and not url.startswith('//') and not url.startswith(self.prefix): url=self.prefix + url.lstrip('/') return match.group('name')+match.group('eq')+match.group('q')+url+match.group('q') self.out.append(_ATTR.sub(replace,raw)) def handle_starttag(self,tag,attrs): self._start() def handle_startendtag(self,tag,attrs): self._start() def handle_endtag(self,tag): self.out.append(f'') def handle_data(self,data): self.out.append(data) def handle_comment(self,data): self.out.append(f'') def handle_decl(self,decl): self.out.append(f'') def handle_entityref(self,name): self.out.append(f'&{name};') def handle_charref(self,name): self.out.append(f'&#{name};') def handle_pi(self,data): self.out.append(f'') def unknown_decl(self,data): self.out.append(f'') def rewrite_preview_html(document: str, site_id: str, build_id: str) -> str: prefix=f'/preview/{site_id}/{build_id}/' parser=_PreviewHTMLParser(prefix) parser.feed(document) parser.close() return ''.join(parser.out)