package render import ( "bytes" "net/url" "path" "path/filepath" "strings" "github.com/microcosm-cc/bluemonday" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/text" ) // rawBase is the path from the rendered page to raw/. func renderMarkdownSafe(src []byte, markdownPath, rawBase string) ([]byte, error) { md := goldmark.New( goldmark.WithExtensions(extension.GFM), goldmark.WithParserOptions(parser.WithAutoHeadingID()), ) doc := md.Parser().Parse(text.NewReader(src)) if err := ast.Walk(doc, func(node ast.Node, entering bool) (ast.WalkStatus, error) { if img, ok := node.(*ast.Image); ok && entering { img.Destination = []byte(markdownImageURL(string(img.Destination), markdownPath, rawBase)) } return ast.WalkContinue, nil }); err != nil { return nil, err } var buf bytes.Buffer if err := md.Renderer().Render(&buf, src, doc); err != nil { return nil, err } policy := bluemonday.UGCPolicy() return policy.SanitizeBytes(buf.Bytes()), nil } func markdownImageURL(destination, markdownPath, rawBase string) string { u, err := url.Parse(destination) if err != nil || u.IsAbs() || u.Host != "" || strings.HasPrefix(destination, "//") || u.Path == "" { return destination } // Resolve within the repository, including repository-root paths. Clamp // parent traversal at its root so images cannot escape the raw revision. imagePath := u.Path if !strings.HasPrefix(imagePath, "/") { imagePath = path.Dir(markdownPath) + "/" + imagePath } imagePath = strings.TrimPrefix(path.Clean("/"+imagePath), "/") u.Path = path.Join(rawBase, imagePath) u.RawPath = "" return u.String() } func isMarkdownPath(path string) bool { name := strings.ToLower(filepath.Base(path)) ext := strings.ToLower(filepath.Ext(path)) return ext == ".md" || name == "readme" } func findReadmePath(tree []TreeEntry) string { for _, e := range tree { if e.Type != "blob" { continue } name := strings.ToLower(e.Path) switch name { case "readme.md", "readme.markdown", "readme": return e.Path } } return "" }