Fix HTML escaping
All checks were successful
/ build (push) Successful in 1m29s

This commit is contained in:
Astra 2026-03-13 14:31:54 +00:00
parent 2675ce1ea0
commit e2faeaac75
2 changed files with 14 additions and 22 deletions

View file

@ -3,6 +3,7 @@ package bsky
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"html"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -329,7 +330,7 @@ func (post *Post) ProcessFacets(aliases []Records) string {
} }
if post.Facets == nil { if post.Facets == nil {
return post.Text return html.EscapeString(post.Text)
} }
sort.Slice((*post.Facets), func(i, j int) bool { sort.Slice((*post.Facets), func(i, j int) bool {
@ -338,18 +339,18 @@ func (post *Post) ProcessFacets(aliases []Records) string {
var result strings.Builder var result strings.Builder
lastIndex := 0 lastIndex := 0
// post.Text = html.EscapeString(post.Text)
for _, facet := range *post.Facets { for _, facet := range *post.Facets {
start := facet.Index.ByteStart start := facet.Index.ByteStart
end := facet.Index.ByteEnd end := facet.Index.ByteEnd
result.WriteString(post.Text[lastIndex:start]) // Escape HTML in plain text portions
result.WriteString(html.EscapeString(post.Text[lastIndex:start]))
for _, feature := range *facet.Features { for _, feature := range *facet.Features {
switch feature.Type { switch feature.Type {
case "app.bsky.richtext.facet#mention": case "app.bsky.richtext.facet#mention":
link := fmt.Sprintf(`<a href="https://bsky.app/profile/%s">%s</a>`, feature.Did, post.Text[start:end]) link := fmt.Sprintf(`<a href="https://bsky.app/profile/%s">%s</a>`, feature.Did, html.EscapeString(post.Text[start:end]))
for _, alias := range aliases { for _, alias := range aliases {
if alias.Value.Subject == feature.Did { if alias.Value.Subject == feature.Did {
link = fmt.Sprintf(`<a href="%s">%s</a>`, link = fmt.Sprintf(`<a href="%s">%s</a>`,
@ -358,18 +359,19 @@ func (post *Post) ProcessFacets(aliases []Records) string {
} }
result.WriteString(link) result.WriteString(link)
case "app.bsky.richtext.facet#link": case "app.bsky.richtext.facet#link":
link := fmt.Sprintf(`<a href="%s">%s</a>`, feature.URI, post.Text[start:end]) link := fmt.Sprintf(`<a href="%s">%s</a>`, feature.URI, html.EscapeString(post.Text[start:end]))
result.WriteString(link) result.WriteString(link)
case "app.bsky.richtext.facet#tag": case "app.bsky.richtext.facet#tag":
link := fmt.Sprintf(`<a href="https://bsky.app/hashtag/%s">%s</a>`, feature.Tag, post.Text[start:end]) link := fmt.Sprintf(`<a href="https://bsky.app/hashtag/%s">%s</a>`, feature.Tag, html.EscapeString(post.Text[start:end]))
result.WriteString(link) result.WriteString(link)
default: default:
result.WriteString(post.Text[start:end]) result.WriteString(html.EscapeString(post.Text[start:end]))
} }
} }
lastIndex = end lastIndex = end
} }
result.WriteString(post.Text[lastIndex:]) // Escape HTML in the final plain text portion
result.WriteString(html.EscapeString(post.Text[lastIndex:]))
return result.String() return result.String()
} }

18
main.go
View file

@ -219,7 +219,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
handle, _ := h.bsky.GetHandleFromDID(strings.Split(ps.Embed.Record.Record.URI, "/")[2]) handle, _ := h.bsky.GetHandleFromDID(strings.Split(ps.Embed.Record.Record.URI, "/")[2])
captionText = fmt.Sprintf( captionText = fmt.Sprintf(
quotePostFormat, quotePostFormat,
escapeHTML(facets), facets,
strings.Split(ps.Embed.Record.Record.URI, "/")[2], strings.Split(ps.Embed.Record.Record.URI, "/")[2],
strings.Split(ps.Embed.Record.Record.URI, "/")[4], strings.Split(ps.Embed.Record.Record.URI, "/")[4],
handle, handle,
@ -230,7 +230,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
handle, _ := h.bsky.GetHandleFromDID(strings.Split(ps.Embed.Record.URI, "/")[2]) handle, _ := h.bsky.GetHandleFromDID(strings.Split(ps.Embed.Record.URI, "/")[2])
captionText = fmt.Sprintf( captionText = fmt.Sprintf(
quotePostFormat, quotePostFormat,
escapeHTML(facets), facets,
strings.Split(ps.Embed.Record.URI, "/")[2], strings.Split(ps.Embed.Record.URI, "/")[2],
strings.Split(ps.Embed.Record.URI, "/")[4], strings.Split(ps.Embed.Record.URI, "/")[4],
handle, handle,
@ -246,7 +246,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
ownHandle = h.bsky.Bluesky.Cfg.Handle ownHandle = h.bsky.Bluesky.Cfg.Handle
} }
if facets != "" { if facets != "" {
captionText = fmt.Sprintf(postFormat, escapeHTML(facets), h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, ownHandle) captionText = fmt.Sprintf(postFormat, facets, h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, ownHandle)
} else { } else {
captionText = fmt.Sprintf("<a href=\"https://bsky.app/profile/%s/post/%s\">🦋 @%s</a>", h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, ownHandle) captionText = fmt.Sprintf("<a href=\"https://bsky.app/profile/%s/post/%s\">🦋 @%s</a>", h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, ownHandle)
} }
@ -328,7 +328,7 @@ func (h *handler) ProcessPost(event *models.Event) error {
} else { } else {
m := tgbotapi.MessageConfig{} m := tgbotapi.MessageConfig{}
if captionText == "" { if captionText == "" {
m = tgbotapi.NewMessage(cid, fmt.Sprintf(postFormat, escapeHTML(facets), h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, h.bsky.Bluesky.Cfg.Handle)) m = tgbotapi.NewMessage(cid, fmt.Sprintf(postFormat, facets, h.bsky.Bluesky.Cfg.DID, event.Commit.RKey, h.bsky.Bluesky.Cfg.Handle))
} else { } else {
m = tgbotapi.NewMessage(cid, captionText) m = tgbotapi.NewMessage(cid, captionText)
} }
@ -360,16 +360,6 @@ func (h *handler) ProcessPost(event *models.Event) error {
return nil return nil
} }
func escapeHTML(text string) string {
// Escape HTML special characters so they display literally
replacements := strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
)
return replacements.Replace(text)
}
func buildBlobURL(server string, did string, cid string) string { func buildBlobURL(server string, did string, cid string) string {
return server + "/xrpc/com.atproto.sync.getBlob?did=" + url.QueryEscape(did) + "&cid=" + cid return server + "/xrpc/com.atproto.sync.getBlob?did=" + url.QueryEscape(did) + "&cid=" + cid
} }