media: resolve {{media:<slug>/<file>}} by searching article folders under subjects; traversal guarded
This commit is contained in:
@@ -443,4 +443,37 @@ func firstSubjects(s []*Subject, n int) []*Subject {
|
||||
return s[:n]
|
||||
}
|
||||
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 ""
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,11 @@ func (s *Site) media(w http.ResponseWriter, r *http.Request, name string) {
|
||||
http.Error(w, "bad media path", http.StatusBadRequest)
|
||||
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)
|
||||
if err != nil {
|
||||
http.Error(w, "media not found", http.StatusNotFound)
|
||||
|
||||
Reference in New Issue
Block a user