diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index a2be0791d46..2973cd612db 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -245,7 +245,7 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { func SecureHeadersMiddleware(next http.Handler, strict bool) http.Handler { // Build img-src CSP directive - imgSrc := "'self' data:image/svg+xml" + imgSrc := "'self' data:" for _, host := range cspImgHost { imgSrc += " " + host diff --git a/gno.land/pkg/gnoweb/markdown/context.go b/gno.land/pkg/gnoweb/markdown/context.go new file mode 100644 index 00000000000..767c5c6a9d0 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/context.go @@ -0,0 +1,21 @@ +package markdown + +import ( + "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" + "github.com/yuin/goldmark/parser" +) + +var gUrlContextKey = parser.NewContextKey() + +// NewGnoParserContext creates a new parser context with GnoURL +func NewGnoParserContext(url *weburl.GnoURL) parser.Context { + ctx := parser.NewContext() + ctx.Set(gUrlContextKey, *url) + return ctx +} + +// getUrlFromContext retrieves the GnoURL from the parser context +func getUrlFromContext(ctx parser.Context) (url weburl.GnoURL, ok bool) { + url, ok = ctx.Get(gUrlContextKey).(weburl.GnoURL) + return +} diff --git a/gno.land/pkg/gnoweb/markdown/ext.go b/gno.land/pkg/gnoweb/markdown/ext.go index 9a7dae837f9..1881cc45123 100644 --- a/gno.land/pkg/gnoweb/markdown/ext.go +++ b/gno.land/pkg/gnoweb/markdown/ext.go @@ -31,37 +31,48 @@ package markdown import ( - "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" "github.com/yuin/goldmark" - "github.com/yuin/goldmark/parser" ) -var _ goldmark.Extender = (*gnoExtension)(nil) +var _ goldmark.Extender = (*GnoExtension)(nil) -// NewGnoParserContext creates a new parser context with GnoURL -func NewGnoParserContext(url *weburl.GnoURL) parser.Context { - ctx := parser.NewContext() - ctx.Set(gUrlContextKey, *url) - return ctx +type GnoExtension struct { + cfg *config } -var gUrlContextKey = parser.NewContextKey() +// Option -// getUrlFromContext retrieves the GnoURL from the parser context -func getUrlFromContext(ctx parser.Context) (url weburl.GnoURL, ok bool) { - url, ok = ctx.Get(gUrlContextKey).(weburl.GnoURL) - return +type config struct { + imgValidatorFunc ImageValidatorFunc } -type gnoExtension struct{} +type Option func(cfg *config) -var GnoExtension = &gnoExtension{} +func WithImageValidator(valFunc ImageValidatorFunc) Option { + return func(cfg *config) { + cfg.imgValidatorFunc = valFunc + } +} + +func NewGnoExtension(opts ...Option) *GnoExtension { + var cfg config + for _, opt := range opts { + opt(&cfg) + } + + return &GnoExtension{&cfg} +} // Extend adds the Gno extension to the provided Goldmark markdown processor. -func (e *gnoExtension) Extend(m goldmark.Markdown) { +func (e *GnoExtension) Extend(m goldmark.Markdown) { // Add column extension - Columns.Extend(m) + ExtColumns.Extend(m) // Add link extension with context - Links.Extend(m) + ExtLinks.Extend(m) + + // If set, setup images filter + if e.cfg.imgValidatorFunc != nil { + ExtImageValidator.Extend(m, e.cfg.imgValidatorFunc) + } } diff --git a/gno.land/pkg/gnoweb/markdown/ext_columns.go b/gno.land/pkg/gnoweb/markdown/ext_columns.go index 866ab7e444f..99cd44a0665 100644 --- a/gno.land/pkg/gnoweb/markdown/ext_columns.go +++ b/gno.land/pkg/gnoweb/markdown/ext_columns.go @@ -298,8 +298,8 @@ func renderGnoColumns(w util.BufWriter, _ []byte, node ast.Node, entering bool) type columns struct{} -// Columns instance for extending markdown with column functionality. -var Columns = &columns{} +// ExtColumns instance for extending markdown with column functionality. +var ExtColumns = &columns{} // Extend adds column functionality to the markdown processor. // XXX: Use 500 for priority for now; we will rework these numbers once another extension is implemented. diff --git a/gno.land/pkg/gnoweb/markdown/ext_imgvalidator.go b/gno.land/pkg/gnoweb/markdown/ext_imgvalidator.go new file mode 100644 index 00000000000..b0028637f79 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/ext_imgvalidator.go @@ -0,0 +1,53 @@ +package markdown + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" +) + +// ImageValidatorFunc validates image URLs. It should return `true` for any valid image URL. +type ImageValidatorFunc func(uri string) (ok bool) + +// imgValidatorTransformer implements ASTTransformer +type imgValidatorTransformer struct { + valFunc ImageValidatorFunc +} + +// Transform iterate on `ast.Image` nodes and validate images URLs. +func (t *imgValidatorTransformer) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) { + if t.valFunc == nil { + return + } + + ast.Walk(doc, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + + img, ok := node.(*ast.Image) + if !ok { + return ast.WalkContinue, nil + } + + if !t.valFunc(string(img.Destination)) { + img.Destination = []byte{} // Erase destination + } + + return ast.WalkContinue, nil + }) +} + +type imgValidatorExtension struct{} + +// ExtImageValidator is a Goldmark extension that pre validation on image URLs. +var ExtImageValidator = &imgValidatorExtension{} + +// Extend adds the ExtImageValidator to the provided Goldmark markdown processor +func (l *imgValidatorExtension) Extend(m goldmark.Markdown, valFunc ImageValidatorFunc) { + m.Parser().AddOptions(parser.WithASTTransformers( + util.Prioritized(&imgValidatorTransformer{valFunc}, 500), + )) +} diff --git a/gno.land/pkg/gnoweb/markdown/ext_links.go b/gno.land/pkg/gnoweb/markdown/ext_links.go index 3595c733990..c0cab82f153 100644 --- a/gno.land/pkg/gnoweb/markdown/ext_links.go +++ b/gno.land/pkg/gnoweb/markdown/ext_links.go @@ -252,8 +252,8 @@ func (r *linkRenderer) renderGnoLink(w util.BufWriter, source []byte, node ast.N // for external, internal, and same-package links. type linkExtension struct{} -// Links instance for extending markdown with link functionality -var Links = &linkExtension{} +// ExtLinks instance for extending markdown with link functionality +var ExtLinks = &linkExtension{} // Extend adds the LinkExtension to the provided Goldmark markdown processor func (l *linkExtension) Extend(m goldmark.Markdown) { diff --git a/gno.land/pkg/gnoweb/markdown/ext_test.go b/gno.land/pkg/gnoweb/markdown/ext_test.go index 883da410ae5..3a2f9717dbf 100644 --- a/gno.land/pkg/gnoweb/markdown/ext_test.go +++ b/gno.land/pkg/gnoweb/markdown/ext_test.go @@ -35,9 +35,13 @@ func testGoldmarkOutput(t *testing.T, nameIn string, input []byte) (string, []by // Create parser context with the test URL ctxOpts := parser.WithContext(NewGnoParserContext(gnourl)) + ext := NewGnoExtension(WithImageValidator(func(uri string) bool { + return !strings.HasPrefix(uri, "https://") // disallow https + })) + // Create markdown processor with extensions and renderer options m := goldmark.New() - GnoExtension.Extend(m) + ext.Extend(m) // Parse markdown input with context node := m.Parser().Parse(text.NewReader(input), ctxOpts) diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_imgvalidator/image_filter.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_imgvalidator/image_filter.md.txtar new file mode 100644 index 00000000000..55ac4aef95a --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_imgvalidator/image_filter.md.txtar @@ -0,0 +1,23 @@ +-- input.md -- +## Normal image link + + +## Data iamge uri + + + +## Empty image uri +![empty img]() + +## Filter any image starting by `https://`, see `ext_text.go` + + +-- output.html -- +
https://
, see ext_text.go