media: resolve {{media:<slug>/<file>}} by searching article folders under subjects; traversal guarded

This commit is contained in:
sam
2026-09-10 09:17:32 +10:00
parent ee80b58352
commit 21bbe25ce2
2 changed files with 39 additions and 2 deletions

View File

@@ -444,3 +444,36 @@ func firstSubjects(s []*Subject, n int) []*Subject {
} }
return s return s
} }
// resolveMediaPath maps a media reference like "chinese-chips/aboutme.jpeg" to an
// existing file under the content root. The reference is relative to the article
// folder, which sits under subjects/<subject>/<slug>/ — so search each subject's
// article folders for the first segment. Returns "" if not found.
func (c *Content) resolveMediaPath(root, name string) string {
// fast path: exact repo-relative path
if data, err := os.ReadFile(path.Join(root, name)); err == nil {
_ = data
return path.Join(root, name)
}
// search: first path element = folder under any subject
i := strings.IndexByte(name, '/')
if i < 0 {
return ""
}
folder := name[:i]
rest := name[i+1:]
subjDir := path.Join(root, "subjects")
if entries, err := os.ReadDir(subjDir); err == nil {
for _, e := range entries {
if !e.IsDir() {
continue
}
candidate := path.Join(subjDir, e.Name(), folder, rest)
if data2, err2 := os.ReadFile(candidate); err2 == nil {
_ = data2
return candidate
}
}
}
return ""
}

View File

@@ -27,7 +27,11 @@ func (s *Site) media(w http.ResponseWriter, r *http.Request, name string) {
http.Error(w, "bad media path", http.StatusBadRequest) http.Error(w, "bad media path", http.StatusBadRequest)
return return
} }
full := path.Join(root, name) full := s.Content.resolveMediaPath(root, name)
if full == "" {
http.Error(w, "media not found", http.StatusNotFound)
return
}
data, err := os.ReadFile(full) data, err := os.ReadFile(full)
if err != nil { if err != nil {
http.Error(w, "media not found", http.StatusNotFound) http.Error(w, "media not found", http.StatusNotFound)