import posixpath import re from urllib.parse import urlparse import bleach from django.conf import settings from markdown import markdown ALLOWED_TAGS = [ "p", "br", "strong", "em", "ul", "ol", "li", "table", "thead", "tbody", "tr", "th", "td", "h1", "h2", "h3", "h4", "a", "code", "pre", "blockquote", "hr", "img", "span", "div", ] ALLOWED_ATTRIBUTES = { "a": ["href", "title"], "img": ["src", "alt", "title"], "th": ["align"], "td": ["align"], } ALLOWED_PROTOCOLS = ["http", "https", "mailto"] EXTENSIONS = ["tables", "fenced_code", "sane_lists"] WIKI_IMAGE_PREFIXES = ("/buyer-guide/", "/ops-guide/") # Links authored in the wiki are absolute: `/en/user-guides/` is a guide that # this help centre mirrors, while any other `/en/` is a wiki-only page. WIKI_GUIDE_PREFIX = "/en/user-guides/" WIKI_PAGE_PREFIX = "/en/" _IMAGE_SRC_RE = re.compile(r'(]*?\bsrc=")([^"]*)(")', re.IGNORECASE) _ANCHOR_HREF_RE = re.compile(r'(]*?\bhref=")([^"]*)(")', re.IGNORECASE) def _rewrite_absolute_anchor(prefix, href, suffix, help_root): """Point an absolute wiki link at somewhere that actually resolves. These hrefs cannot be left alone. Every app host serves only its own portal (/portal/, /ops/, /admin/) and has no `/en/` route, so an untouched `/en/...` link is a 404 for whoever clicks it inside the help centre. - `/en/user-guides/` is mirrored in-app -> the in-app help path. - any other `/en/` exists only in the wiki -> the wiki public base. """ path = href.split("#", 1)[0].split("?", 1)[0] if path.startswith(WIKI_GUIDE_PREFIX): target = path[len(WIKI_GUIDE_PREFIX):].strip("/") if target: return f'{prefix}{help_root}{target}/{suffix}' return f'{prefix}{help_root}{suffix}' if path.startswith(WIKI_PAGE_PREFIX): return f'{prefix}{settings.WIKI_PUBLIC_BASE}{path}{suffix}' return None def _rewrite_anchor(match, base_folder, help_root): prefix, href, suffix = match.groups() parsed = urlparse(href) if parsed.scheme or href.startswith("#"): return match.group(0) if href.startswith("/"): rewritten = _rewrite_absolute_anchor(prefix, href, suffix, help_root) return rewritten if rewritten is not None else match.group(0) resolved = posixpath.normpath(posixpath.join(base_folder, href)) if resolved.startswith("user-guides/"): suffix_path = resolved[len("user-guides/"):] return f'{prefix}{help_root}{suffix_path}/{suffix}' return match.group(0) def _rewrite_image(match): prefix, src, suffix = match.groups() if src.startswith(WIKI_IMAGE_PREFIXES): return f'{prefix}{settings.WIKI_PUBLIC_BASE}{src}{suffix}' return match.group(0) def render_markdown(content, page_path, help_root): html = markdown(content or "", extensions=EXTENSIONS) base_folder = posixpath.dirname(page_path) html = _ANCHOR_HREF_RE.sub(lambda m: _rewrite_anchor(m, base_folder, help_root), html) html = _IMAGE_SRC_RE.sub(_rewrite_image, html) return bleach.clean( html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, protocols=ALLOWED_PROTOCOLS, )