// Package static —— 虚拟主机 Web Hosting 模式下针对 Markdown 的服务端预览渲染。 // // 设计原则: // 1. 不引入任何新的第三方依赖。Markdown 渲染交给浏览器端 marked.js(CDN)。 // 2. 渲染有 size 上限(默认 5MB),防止从云端读取超大文件造成 OOM。 // 3. Markdown 原文嵌入到 ,也会因 type 非 JS 而不被执行;同时对 0 && size > maxBytes { return nil, fmt.Errorf("file too large for render: size=%d max=%d", size, maxBytes) } rr, err := stream.GetRangeReaderFromLink(size, link) if err != nil { return nil, fmt.Errorf("get range reader: %w", err) } // 当 size 未知时使用全量范围(Length=-1 由底层处理) rng := http_range.Range{Start: 0, Length: -1} if size > 0 { rng = http_range.Range{Start: 0, Length: size} } rc, err := rr.RangeRead(ctx, rng) if err != nil { return nil, fmt.Errorf("range read: %w", err) } defer rc.Close() // 用 LimitReader 兜底防止 size 申报为 0 但内容超大 limited := io.LimitReader(rc, maxBytes+1) buf, err := io.ReadAll(limited) if err != nil { return nil, fmt.Errorf("read all: %w", err) } if int64(len(buf)) > maxBytes { return nil, fmt.Errorf("file exceeds max bytes during read: max=%d", maxBytes) } return buf, nil } // markdownPreviewTpl 是 Markdown 全屏预览的 HTML 模板。 // 使用 marked.js + highlight.js + DOMPurify 在浏览器端渲染,样式参考 GitHub Markdown CSS。 // 占位符: // // {{TITLE}} 标签内容(页面文件名,已 HTML 转义) // {{MD_BODY}} 原始 Markdown 文本(已对 </ 做 <\/ 转义防止 script 标签提前闭合) const markdownPreviewTpl = `<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>{{TITLE}}

Rendering Markdown...

` // renderMarkdownPreview 把 Markdown 原文包装为完整的 HTML 预览页。 // 注意:原文必须做 中提前闭合。 func renderMarkdownPreview(filename string, mdSource []byte) []byte { // HTML-escape title(防止文件名注入 HTML) title := htmlEscape(filename) // 对 块在 HTML 解析阶段 // 会按"原始文本"处理,唯一会让它结束的是 ", ">", `"`, """, "'", "'", ) return r.Replace(s) } // writeRenderedHTML 把渲染后的 HTML 作为 200 响应写出。 func writeRenderedHTML(w http.ResponseWriter, html []byte) { h := w.Header() h.Set("Content-Type", "text/html; charset=utf-8") h.Set("Content-Disposition", "inline") // Markdown 内容不常变化,允许浏览器缓存但每次需验证 h.Set("Cache-Control", "no-cache, must-revalidate") h.Set("X-Content-Type-Options", "nosniff") w.WriteHeader(http.StatusOK) if _, err := w.Write(html); err != nil { utils.Log.Debugf("[VirtualHost] writeRenderedHTML: %v", err) } }