diff --git a/src/shared/components/post/metadata-card.tsx b/src/shared/components/post/metadata-card.tsx index 1269c0b7..1d8e345b 100644 --- a/src/shared/components/post/metadata-card.tsx +++ b/src/shared/components/post/metadata-card.tsx @@ -1,6 +1,7 @@ import { Component } from "inferno"; import { Post } from "lemmy-js-client"; import * as sanitizeHtml from "sanitize-html"; +import { unescapeHTML } from "@utils/helpers"; import { relTags } from "../../config"; import { Icon } from "../common/icon"; @@ -25,17 +26,21 @@ export class MetadataCard extends Component { {post.name !== post.embed_title && ( <>
- - {post.embed_title} + + {unescapeHTML(post.embed_title)}
- {new URL(post.url).hostname} + {new URL(unescapeHTML(post.url)).hostname} diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index afd5daa5..64b9e993 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -5,6 +5,7 @@ import { capitalizeFirstLetter, futureDaysToUnixTime, hostname, + unescapeHTML, } from "@utils/helpers"; import { isImage, isVideo } from "@utils/media"; import { @@ -206,9 +207,9 @@ export class PostListing extends Component { <> {this.listing()} {this.state.imageExpanded && !this.props.hideImage && this.img} - {this.showBody && post.url && post.embed_title && ( - - )} + {this.showBody && + unescapeHTML(post.url) && + unescapeHTML(post.embed_title) && } {this.showBody && this.body()} ) : ( @@ -285,8 +286,8 @@ export class PostListing extends Component { ); @@ -309,7 +310,7 @@ export class PostListing extends Component { get imageSrc(): string | undefined { const post = this.postView.post; - const url = post.url; + const url = unescapeHTML(post.url); const thumbnail = post.thumbnail_url; if (thumbnail) { @@ -323,7 +324,7 @@ export class PostListing extends Component { thumbnail() { const post = this.postView.post; - const url = post.url; + const url = unescapeHTML(post.url); const thumbnail = post.thumbnail_url; if (!this.props.hideImage && url && isImage(url) && this.imageSrc) { @@ -449,7 +450,7 @@ export class PostListing extends Component { > ); @@ -457,7 +458,7 @@ export class PostListing extends Component { postTitleLine() { const post = this.postView.post; - const url = post.url; + const url = unescapeHTML(post.url); return ( <> @@ -473,7 +474,9 @@ export class PostListing extends Component { href={url} title={url} rel={relTags} - dangerouslySetInnerHTML={mdToHtmlInline(post.name)} + dangerouslySetInnerHTML={mdToHtmlInline( + unescapeHTML(post.name), + )} > ) : ( this.postLink @@ -486,7 +489,7 @@ export class PostListing extends Component { * MetadataCard/body toggle. */} {!this.props.showBody && - ((post.url && post.embed_title) || post.body) && + (unescapeHTML(post.url && post.embed_title) || post.body) && this.showPreviewButton()} {post.removed && ( @@ -548,7 +551,7 @@ export class PostListing extends Component { urlLine() { const post = this.postView.post; - const url = post.url; + const url = unescapeHTML(post.url); return (

diff --git a/src/shared/components/post/post.tsx b/src/shared/components/post/post.tsx index 1b18c9c4..c624f076 100644 --- a/src/shared/components/post/post.tsx +++ b/src/shared/components/post/post.tsx @@ -19,7 +19,7 @@ import { restoreScrollPosition, saveScrollPosition, } from "@utils/browser"; -import { debounce, randomStr } from "@utils/helpers"; +import { debounce, randomStr, unescapeHTML } from "@utils/helpers"; import { isImage } from "@utils/media"; import { RouteDataResponse } from "@utils/types"; import autosize from "autosize"; @@ -325,8 +325,9 @@ export class Post extends Component { get documentTitle(): string { const siteName = this.state.siteRes.site_view.site.name; + const postTitle = unescapeHTML(this.state.postRes.data.post_view.post.name); return this.state.postRes.state === "success" - ? `${this.state.postRes.data.post_view.post.name} - ${siteName}` + ? `${postTitle} - ${siteName}` : siteName; } diff --git a/src/shared/utils/helpers/html-entities.ts b/src/shared/utils/helpers/html-entities.ts new file mode 100644 index 00000000..3cfe95d3 --- /dev/null +++ b/src/shared/utils/helpers/html-entities.ts @@ -0,0 +1,60 @@ +const matchEscHtmlRx = /["'&<>]/; +const matchUnEscRx = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g; + +export function escapeHTML(str: string): string { + const matchEscHtml = matchEscHtmlRx.exec(str); + if (!matchEscHtml) { + return str; + } + let escape; + let html = ""; + let index = 0; + let lastIndex = 0; + for (index = matchEscHtml.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = """; + break; + case 38: // & + escape = "&"; + break; + case 39: // ' + escape = "'"; + break; + case 60: // < + escape = "<"; + break; + case 62: // > + escape = ">"; + break; + default: + continue; + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index); + } + + lastIndex = index + 1; + html += escape; + } + + return lastIndex !== index ? html + str.substring(lastIndex, index) : html; +} + +export function unescapeHTML(str: string): string { + const matchUnEsc = matchUnEscRx.exec(str); + if (!matchUnEsc) { + return str; + } + + const res = str + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/:/g, ":") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/&/g, "&"); + + return unescapeHTML(res); +} diff --git a/src/shared/utils/helpers/index.ts b/src/shared/utils/helpers/index.ts index c42f9109..aedfef0c 100644 --- a/src/shared/utils/helpers/index.ts +++ b/src/shared/utils/helpers/index.ts @@ -18,16 +18,19 @@ import numToSI from "./num-to-si"; import poll from "./poll"; import randomStr from "./random-str"; import removeAuthParam from "./remove-auth-param"; +import returnStringFromString from "./return-str"; import sleep from "./sleep"; import validEmail from "./valid-email"; import validInstanceTLD from "./valid-instance-tld"; import validTitle from "./valid-title"; import validURL from "./valid-url"; +import { escapeHTML, unescapeHTML } from "./html-entities"; export { capitalizeFirstLetter, debounce, editListImmutable, + escapeHTML, formatPastDate, futureDaysToUnixTime, getIdFromString, @@ -45,7 +48,9 @@ export { poll, randomStr, removeAuthParam, + returnStringFromString, sleep, + unescapeHTML, validEmail, validInstanceTLD, validTitle,