From cc9cd5ef286033ea34cf2e96b7a9ef253b83a8ca Mon Sep 17 00:00:00 2001 From: Matchu Date: Mon, 6 Sep 2021 14:05:36 -0700 Subject: [PATCH] Rewrite old closet links to use new syntax A lot of DTI lists use old URLs to anchor-link between lists! Here, we rewrite those URLs to match what DTI 2020 expects, so that they actually correctly jump you across the page and aren't filtered out! --- src/app/UserItemListPage.js | 56 ++++++++++++++++++++++- src/app/components/MarkdownAndSafeHTML.js | 8 +++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/app/UserItemListPage.js b/src/app/UserItemListPage.js index 311071f..402b7fc 100644 --- a/src/app/UserItemListPage.js +++ b/src/app/UserItemListPage.js @@ -349,7 +349,9 @@ export function ClosetList({ boxShadow="sm" /> ) : ( - {closetList.description} + + {closetList.description} + )} )} @@ -556,4 +558,56 @@ export function NeopetsStarIcon(props) { ); } +/** + * Rewrite old-style links from Classic DTI, for compatibility with our new + * URLs and anchor syntax. + */ +function rewriteLinkAnchors(node) { + try { + // Only rewrite `a` tags, and only if they point to DTI (either classic + // DTI, or a path relative to the current host.) + if (node.nodeName === "A") { + const url = new URL(node.href); + if ( + url.host === "impress.openneo.net" || + url.host === window.location.host + ) { + // Rewrite the `closet` path component to `lists`. + const closetPathMatch = url.pathname.match(/^\/user\/([^/]+)\/closet$/); + if (closetPathMatch) { + url.pathname = `/user/${closetPathMatch[1]}/lists`; + } + + // Rewrite `#closet-list-123` to `#list-123`. + const closetListAnchorMatch = url.hash.match(/^#closet-list-(.+)$/); + if (closetListAnchorMatch) { + url.hash = "#list-" + closetListAnchorMatch[1]; + } + + // Rewrite `#closet-hangers-group-true` to `#owned-items`. + if (url.hash === "#closet-hangers-group-true") { + url.hash = "#owned-items"; + } + + // Rewrite `#closet-hangers-group-false` to `#wanted-items`. + if (url.hash === "#closet-hangers-group-false") { + url.hash = "#wanted-items"; + } + + // Set the href to be an absolute path to the new URL. (We do this + // rather than url.toString(), because that will return a full absolute + // URL, and DOMPurify might not have the current host in its allow list, + // e.g. in dev or in a preview deploy.) + node.href = url.pathname + url.search + url.hash; + } + } + } catch (e) { + // None of these operations are safety operations; they're just to make + // old-style links compatible. If processing fails, then they'll just be + // judged for safety by DOMPurify and deleted if unsafe, which is fine! + // So, we allow processing to succeed, and just log the error. + console.error("[rewriteLinkAnchors] Could not rewrite node", node, e); + } +} + export default UserItemListPage; diff --git a/src/app/components/MarkdownAndSafeHTML.js b/src/app/components/MarkdownAndSafeHTML.js index b97bdb7..1093922 100644 --- a/src/app/components/MarkdownAndSafeHTML.js +++ b/src/app/components/MarkdownAndSafeHTML.js @@ -37,11 +37,14 @@ const unsafeMarkdownOutput = SimpleMarkdown.htmlFor( * Rendering this component *should* be XSS-safe, it's designed to strip out * bad things! Still, be careful when using it, and consider what you're doing! */ -function MarkdownAndSafeHTML({ children }) { +function MarkdownAndSafeHTML({ children, onBeforeSanitizeNode }) { const htmlAndMarkdown = children; const unsafeHtml = unsafeMarkdownOutput(markdownParser(htmlAndMarkdown)); + if (onBeforeSanitizeNode) { + DOMPurify.addHook("beforeSanitizeAttributes", onBeforeSanitizeNode); + } const sanitizedHtml = DOMPurify.sanitize(unsafeHtml, { ALLOWED_TAGS: [ "b", @@ -62,6 +65,9 @@ function MarkdownAndSafeHTML({ children }) { // slash or hash (internal link). ALLOWED_URI_REGEXP: /^https?:\/\/(impress\.openneo\.net|impress-2020\.openneo\.net|www\.neopets\.com|neopets\.com|items\.jellyneo\.net)\/|^[/#]/, }); + if (onBeforeSanitizeNode) { + DOMPurify.removeHook("beforeSanitizeAttributes"); + } return (