summaryrefslogtreecommitdiff
path: root/app/parsers.go
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-15 03:17:09 -0500
committerChristian Cleberg <[email protected]>2026-07-15 03:17:09 -0500
commit83d9cd0b7a3e0ec6beb5c889102211567a6db03f (patch)
tree59926c27df295ba506d6f08a901274dd6d7c300c /app/parsers.go
parent8d08f343c930f556c6ab016be9d00b23f1e516e3 (diff)
downloadskunky-art-83d9cd0b7a3e0ec6beb5c889102211567a6db03f.tar.gz
skunky-art-83d9cd0b7a3e0ec6beb5c889102211567a6db03f.tar.bz2
skunky-art-83d9cd0b7a3e0ec6beb5c889102211567a6db03f.zip
fix: serve media again by unsetting download-proxy and scoping Host per request
Two independent faults made every image fail while pages still rendered. config.example.json shipped download-proxy=http://127.0.0.1:8080. Only media fetches go through that proxy — pages reach DeviantArt via devianter on the default transport — so when nothing listens there, images 502 and the rest of the page looks fine. In a scratch container 127.0.0.1 is the container itself, so the default could never work under Docker. Unset it and document that it must stay empty unless an operator really runs a proxy. Host was a package global reassigned by every request, so a concurrent request could overwrite it mid-render and emit URLs on another origin's host and port. The instance's own default-src 'self' CSP then blocked those images. Thread the request's host through skunkyart instead, and take it as an explicit argument in URLBuilder, ParseMedia, ParseDescription, BuildUserPlate and ConvertDeviantArtURLToSkunkyArt. Feeds keep their absolute URLs. Also start RefreshInstances after ExecuteConfig rather than before it: the goroutine read CFG while json.Unmarshal was writing it (a race the detector flags), and its fetch escaped both the throttle and the configured User-Agent. Verified: 300 concurrent requests with distinct Host headers now round-trip their own host (was 1 leak per 300), go test -race is clean, and cache+proxy both enabled serves 200 image/jpeg cold and from cache.
Diffstat (limited to 'app/parsers.go')
-rwxr-xr-xapp/parsers.go27
1 files changed, 14 insertions, 13 deletions
diff --git a/app/parsers.go b/app/parsers.go
index 5f92658..e5fd0d1 100755
--- a/app/parsers.go
+++ b/app/parsers.go
@@ -32,9 +32,9 @@ func (s skunkyart) ParseComments(c devianter.Comments, daError devianter.Error)
cmmts.WriteString(`"><p id="`)
cmmts.WriteString(strconv.Itoa(x.ID))
cmmts.WriteString(`"><img src="`)
- cmmts.WriteString(URLBuilder("media", "emojitar", x.User.Username, "?type=a"))
+ cmmts.WriteString(URLBuilder(s.Host, "media", "emojitar", x.User.Username, "?type=a"))
cmmts.WriteString(`" width="30px" height="30px"><a href="`)
- cmmts.WriteString(URLBuilder("group_user", "?q=", x.User.Username, "&type=a"))
+ cmmts.WriteString(URLBuilder(s.Host, "group_user", "?q=", x.User.Username, "&type=a"))
cmmts.WriteString(`"><b`)
cmmts.WriteString(` class="`)
if x.User.Banned {
@@ -64,7 +64,7 @@ func (s skunkyart) ParseComments(c devianter.Comments, daError devianter.Error)
cmmts.WriteString(x.Posted.UTC().String())
cmmts.WriteString("]<p>")
- cmmts.WriteString(ParseDescription(x.TextContent))
+ cmmts.WriteString(ParseDescription(s.Host, x.TextContent))
cmmts.WriteString("<p>👍: ")
cmmts.WriteString(strconv.Itoa(x.Likes))
cmmts.WriteString(" ⏩: ")
@@ -92,7 +92,7 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con
for i, l := 0, len(devs); i < l; i++ {
data := &devs[i]
- if preview, fullview := ParseMedia(data.Media, 320), ParseMedia(data.Media); !data.NSFW || CFG.Nsfw {
+ if preview, fullview := ParseMedia(s.Host, data.Media, 320), ParseMedia(s.Host, data.Media); !data.NSFW || CFG.Nsfw {
if allowAtom && s.Atom {
s.Writer.Header().Add("Content-Type", "application/atom+xml")
id := strconv.Itoa(data.ID)
@@ -101,7 +101,7 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con
listContent.WriteString(`</name></author><title>`)
listContent.WriteString(data.Title)
listContent.WriteString(`</title><link rel="alternate" type="text/html" href="`)
- listContent.WriteString(URLBuilder("post", data.Author.Username, "atom-"+id))
+ listContent.WriteString(URLBuilder(s.Host, "post", data.Author.Username, "atom-"+id))
listContent.WriteString(`"/><id>`)
listContent.WriteString(id)
listContent.WriteString(`</id><published>`)
@@ -112,11 +112,11 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con
listContent.WriteString(`</media:title><media:thumbinal url="`)
listContent.WriteString(preview)
listContent.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`)
- listContent.WriteString(ConvertDeviantArtURLToSkunkyArt(data.Url))
+ listContent.WriteString(ConvertDeviantArtURLToSkunkyArt(s.Host, data.Url))
listContent.WriteString(`"><img src="`)
listContent.WriteString(fullview)
listContent.WriteString(`"/></a><p>`)
- listContent.WriteString(ParseDescription(data.TextContent))
+ listContent.WriteString(ParseDescription(s.Host, data.TextContent))
listContent.WriteString(`</p></div></content></entry>`)
} else {
listContent.WriteString(`<div class="block">`)
@@ -130,7 +130,7 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con
listContent.WriteString(`<h1>[ TEXT ]</h1>`)
}
listContent.WriteString(`<br><a href="`)
- listContent.WriteString(ConvertDeviantArtURLToSkunkyArt(data.Url))
+ listContent.WriteString(ConvertDeviantArtURLToSkunkyArt(s.Host, data.Url))
listContent.WriteString(`">`)
listContent.WriteString(data.Author.Username)
listContent.WriteString(" - ")
@@ -166,7 +166,7 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con
list.WriteString(`</title>`)
list.WriteString(`<link rel="alternate" href="`)
- list.WriteString(Host)
+ list.WriteString(s.Host)
list.WriteString(`"/>`)
list.WriteString(listContent.String())
@@ -201,9 +201,10 @@ type text struct {
// ParseDescription renders a DeviantArt description into HTML, handling both the
// Draft.js-style JSON payload and the plain HTML markup DeviantArt returns, and
// rewriting embedded links and artwork references to point at this instance.
+// host is the request's scheme and host, as taken by URLBuilder.
//
// TODO: rewrite this whole mess.
-func ParseDescription(dscr devianter.Text) string {
+func ParseDescription(host string, dscr devianter.Text) string {
var parsedDescription strings.Builder
TagBuilder := func(content string, tags ...string) string {
l := len(tags)
@@ -308,9 +309,9 @@ func ParseDescription(dscr devianter.Text) string {
if len(x.EntityRanges) != 0 {
d := entities[x.EntityRanges[0].Key]
parsedDescription.WriteString(`<a href="`)
- parsedDescription.WriteString(ConvertDeviantArtURLToSkunkyArt(d.Url))
+ parsedDescription.WriteString(ConvertDeviantArtURLToSkunkyArt(host, d.Url))
parsedDescription.WriteString(`"><img width="50%" src="`)
- parsedDescription.WriteString(ParseMedia(d.Media))
+ parsedDescription.WriteString(ParseMedia(host, d.Media))
parsedDescription.WriteString(`" title="`)
parsedDescription.WriteString(d.Author.Username)
parsedDescription.WriteString(" - ")
@@ -372,7 +373,7 @@ func ParseDescription(dscr devianter.Text) string {
switch a.Key {
case "src":
if len(a.Val) > 9 && a.Val[8:9] == "e" {
- uri = URLBuilder("media", "emojitar", a.Val[37:len(a.Val)-4], "?type=e")
+ uri = URLBuilder(host, "media", "emojitar", a.Val[37:len(a.Val)-4], "?type=e")
}
case "title":
title = a.Val