fix(httpapi): no-store 接线、图片缓存头时序、解压炸弹上限与 RSS 空日期
/api/admin/* 挂 NoStore、/api/me 内联 no-store(P1-3); 图片缓存头移至数据读取成功后,404 不携带 public immutable(P2-9); 上传先 DecodeConfig 限制像素 ≤2^25 再解码(P2-12); 无公开笔记时省略 lastBuildDate(P2-10)。
This commit is contained in:
+26
-10
@@ -18,7 +18,9 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// handleMe GET /api/me:匿名 {authenticated:false};已认证 {authenticated:true, csrf_token}。
|
||||
// 响应随会话态变化且含 CSRF token → no-store。
|
||||
func (s *Server) handleMe(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
if sess, ok := s.sessionFrom(r); ok {
|
||||
s.maybeRotate(w, sess)
|
||||
writeJSON(w, http.StatusOK, map[string]any{"authenticated": true, "csrf_token": sess.CSRFToken})
|
||||
@@ -100,8 +102,18 @@ func (s *Server) handlePublicNote(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
prevSlug, prevTitle, _ := s.st.AdjacentPublicNote(note, "prev")
|
||||
nextSlug, nextTitle, _ := s.st.AdjacentPublicNote(note, "next")
|
||||
prevSlug, prevTitle, err := s.st.AdjacentPublicNote(note, "prev")
|
||||
if err != nil {
|
||||
s.log.Error("查询上一篇失败", "err", err)
|
||||
writeError(w, http.StatusInternalServerError, "internal", "内部错误")
|
||||
return
|
||||
}
|
||||
nextSlug, nextTitle, err := s.st.AdjacentPublicNote(note, "next")
|
||||
if err != nil {
|
||||
s.log.Error("查询下一篇失败", "err", err)
|
||||
writeError(w, http.StatusInternalServerError, "internal", "内部错误")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"id": note.ID, "slug": note.Slug, "title": note.Title,
|
||||
"summary": note.Summary, "content": note.Content,
|
||||
@@ -133,7 +145,8 @@ func (s *Server) handleTags(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// handleImage GET /api/images/{id}:并集可见性;未授权与不存在统一 404;
|
||||
// 缓存头按可见性分流(§7.4)。
|
||||
// 缓存头按可见性分流(§7.4)。缓存头在数据读取成功后才设置,
|
||||
// 错误路径不携带公开缓存指令(防 404 被 CDN 缓存一年)。
|
||||
func (s *Server) handleImage(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
@@ -156,10 +169,18 @@ func (s *Server) handleImage(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusNotFound, "not_found", "图片不存在")
|
||||
return
|
||||
}
|
||||
}
|
||||
// 先取数据:数据行竞态缺失时返回的 404 不携带任何缓存指令
|
||||
data, err := s.st.GetImageData(id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "not_found", "图片不存在")
|
||||
return
|
||||
}
|
||||
if public {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
} else {
|
||||
// 非公开图:每次请求重新判定,禁止缓存
|
||||
w.Header().Set("Cache-Control", "private, no-store")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
}
|
||||
w.Header().Set("Content-Type", img.MIME)
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
@@ -168,11 +189,6 @@ func (s *Server) handleImage(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
data, err := s.st.GetImageData(id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "not_found", "图片不存在")
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user