From 7edbc4468630c405fcf6c8a865c32dac5adb5146 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Fri, 02 May 2025 19:49:58 +0000 Subject: [PATCH] appview: pages/markup: rewrite links links to camo url --- appview/config.go | 2 ++ appview/pages/funcmap.go | 2 +- appview/pages/markup/camo.go | 31 +++++++++++++++++++++++++++++++ appview/pages/markup/markdown.go | 13 ++++++++++++- appview/pages/pages.go | 23 ++++++++++------------- appview/state/state.go | 2 +- 6 file(s) changed, 57 insertion(s)(+), 16 deletion(s)(-) diff --git a/appview/config.go b/appview/config.go --- a/appview/config.go +++ b/appview/config.go @@ -13,6 +13,8 @@ ListenAddr string `env:"TANGLED_LISTEN_ADDR, default=0.0.0.0:3000"` Dev bool `env:"TANGLED_DEV, default=false"` JetstreamEndpoint string `env:"TANGLED_JETSTREAM_ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"` ResendApiKey string `env:"TANGLED_RESEND_API_KEY"` + CamoHost string `env:"TANGLED_CAMO_HOST, default=https://camo.tangled.sh"` + CamoSharedSecret string `env:"TANGLED_CAMO_SHARED_SECRET"` } func LoadConfig(ctx context.Context) (*Config, error) { diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go --- a/appview/pages/funcmap.go +++ b/appview/pages/funcmap.go @@ -143,7 +143,7 @@ } return v.Slice(start, end).Interface() }, "markdown": func(text string) template.HTML { - rctx := &markup.RenderContext{} + rctx := &markup.RenderContext{RendererType: markup.RendererTypeDefault} return template.HTML(rctx.RenderMarkdown(text)) }, "isNil": func(t any) bool { diff --git a/appview/pages/markup/camo.go b/appview/pages/markup/camo.go new file mode 100644 --- /dev/null +++ b/appview/pages/markup/camo.go @@ -0,0 +1,31 @@ +package markup + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + + "github.com/yuin/goldmark/ast" +) + +func generateCamoURL(baseURL, secret, imageURL string) string { + h := hmac.New(sha256.New, []byte(secret)) + h.Write([]byte(imageURL)) + signature := hex.EncodeToString(h.Sum(nil)) + hexURL := hex.EncodeToString([]byte(imageURL)) + return fmt.Sprintf("%s/%s/%s", baseURL, signature, hexURL) +} + +func (rctx *RenderContext) camoImageLinkTransformer(img *ast.Image) { + // don't camo on dev + if rctx.IsDev { + return + } + + dst := string(img.Destination) + + if rctx.CamoUrl != "" && rctx.CamoSecret != "" { + img.Destination = []byte(generateCamoURL(rctx.CamoUrl, rctx.CamoSecret, dst)) + } +} diff --git a/appview/pages/markup/markdown.go b/appview/pages/markup/markdown.go --- a/appview/pages/markup/markdown.go +++ b/appview/pages/markup/markdown.go @@ -21,11 +21,15 @@ const ( // RendererTypeRepoMarkdown is for repository documentation markdown files RendererTypeRepoMarkdown RendererType = iota + // RendererTypeDefault is non-repo markdown, like issues/pulls/comments. + RendererTypeDefault ) // RenderContext holds the contextual data for rendering markdown. // It can be initialized empty, and that'll skip any transformations. type RenderContext struct { + CamoUrl string + CamoSecret string repoinfo.RepoInfo IsDev bool RendererType RendererType @@ -73,8 +77,15 @@ case *ast.Link: a.rctx.relativeLinkTransformer(n.(*ast.Link)) case *ast.Image: a.rctx.imageFromKnotTransformer(n.(*ast.Image)) + a.rctx.camoImageLinkTransformer(n.(*ast.Image)) } - // more types here like RendererTypeIssue/Pull etc. + + case RendererTypeDefault: + switch n.(type) { + case *ast.Image: + a.rctx.imageFromKnotTransformer(n.(*ast.Image)) + a.rctx.camoImageLinkTransformer(n.(*ast.Image)) + } } return ast.WalkContinue, nil diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -15,6 +15,7 @@ "os" "path/filepath" "strings" + "tangled.sh/tangled.sh/core/appview" "tangled.sh/tangled.sh/core/appview/auth" "tangled.sh/tangled.sh/core/appview/db" "tangled.sh/tangled.sh/core/appview/pages/markup" @@ -44,15 +45,17 @@ templateDir string // Path to templates on disk for dev mode rctx *markup.RenderContext } -func NewPages(dev bool) *Pages { +func NewPages(config *appview.Config) *Pages { // initialized with safe defaults, can be overriden per use rctx := &markup.RenderContext{ - IsDev: dev, + IsDev: config.Dev, + CamoUrl: config.CamoHost, + CamoSecret: config.CamoSharedSecret, } p := &Pages{ t: make(map[string]*template.Template), - dev: dev, + dev: config.Dev, embedFS: Files, rctx: rctx, templateDir: "appview/pages", @@ -380,11 +383,8 @@ if params.IsEmpty { return p.executeRepo("repo/empty", w, params) } - p.rctx = &markup.RenderContext{ - RepoInfo: params.RepoInfo, - IsDev: p.dev, - RendererType: markup.RendererTypeRepoMarkdown, - } + p.rctx.RepoInfo = params.RepoInfo + p.rctx.RendererType = markup.RendererTypeRepoMarkdown if params.ReadmeFileName != "" { var htmlString string @@ -521,11 +521,8 @@ if params.ShowRendered { switch markup.GetFormat(params.Path) { case markup.FormatMarkdown: - p.rctx = &markup.RenderContext{ - RepoInfo: params.RepoInfo, - IsDev: p.dev, - RendererType: markup.RendererTypeRepoMarkdown, - } + p.rctx.RepoInfo = params.RepoInfo + p.rctx.RendererType = markup.RendererTypeRepoMarkdown params.RenderedContents = template.HTML(p.rctx.RenderMarkdown(params.Contents)) } } diff --git a/appview/state/state.go b/appview/state/state.go --- a/appview/state/state.go +++ b/appview/state/state.go @@ -55,7 +55,7 @@ } clock := syntax.NewTIDClock(0) - pgs := pages.NewPages(config.Dev) + pgs := pages.NewPages(config) resolver := appview.NewResolver() -- tangled.sh