Repository: AlistGo/alist (language: go)

Issue:
无法读取 Markdown 格式(不知道这么描述是否正确)
### Please make sure of the following things

- [x] I have read the [documentation](https://alist.nn.ci).
我已经阅读了[文档](https://alist.nn.ci)。

- [x] I'm sure there are no duplicate issues or discussions.
我确定没有重复的issue或讨论。

- [x] I'm sure it's due to `AList` and not something else(such as [Network](https://alist.nn.ci/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host) ,`Dependencies` or `Operational`).
我确定是`AList`的问题，而不是其他原因（例如[网络](https://alist.nn.ci/zh/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host)，`依赖`或`操作`）。

- [x] I'm sure this issue is not fixed in the latest version.
我确定这个问题在最新版本中没有被修复。


### AList Version / AList 版本

v3.43.0

### Driver used / 使用的存储驱动

本地

### Describe the bug / 问题描述

我将我科学上网的节点信息经过base64加密后，放置 alist 目录以xx.md 使用，两小时前的这个版本更新后，passwall 等软件无法读取 xx.md，更改 md文件后缀成其他任何文本后缀都可以读取。不知道这个版本是否有什么机制修改？

### Reproduction / 复现链接

使用两个 docker 容器均一样的问题。

### Config / 配置

/

### Logs / 日志

_No response_


Discussion / hints:

Fix patch:
diff --git a/drivers/quqi/driver.go b/drivers/quqi/driver.go
index 0fa64041d26..36758bd1d14 100644
--- a/drivers/quqi/driver.go
+++ b/drivers/quqi/driver.go
@@ -316,7 +316,7 @@ func (d *Quqi) Put(ctx context.Context, dstDir model.Obj, stream model.FileStrea
 	// if the file already exists in Quqi server, there is no need to actually upload it
 	if uploadInitResp.Data.Exist {
 		// the file name returned by Quqi does not include the extension name
-		nodeName, nodeExt := uploadInitResp.Data.NodeName, rawExt(stream.GetName())
+		nodeName, nodeExt := uploadInitResp.Data.NodeName, utils.Ext(stream.GetName())
 		if nodeExt != "" {
 			nodeName = nodeName + "." + nodeExt
 		}
@@ -432,7 +432,7 @@ func (d *Quqi) Put(ctx context.Context, dstDir model.Obj, stream model.FileStrea
 		return nil, err
 	}
 	// the file name returned by Quqi does not include the extension name
-	nodeName, nodeExt := uploadFinishResp.Data.NodeName, rawExt(stream.GetName())
+	nodeName, nodeExt := uploadFinishResp.Data.NodeName, utils.Ext(stream.GetName())
 	if nodeExt != "" {
 		nodeName = nodeName + "." + nodeExt
 	}
diff --git a/drivers/quqi/util.go b/drivers/quqi/util.go
index 5ad43c4b97b..aa184d70fc4 100644
--- a/drivers/quqi/util.go
+++ b/drivers/quqi/util.go
@@ -9,7 +9,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	stdpath "path"
 	"strings"
 	"time"
 
@@ -115,16 +114,6 @@ func (d *Quqi) checkLogin() bool {
 	return true
 }
 
-// rawExt 保留扩展名大小写
-func rawExt(name string) string {
-	ext := stdpath.Ext(name)
-	if strings.HasPrefix(ext, ".") {
-		ext = ext[1:]
-	}
-
-	return ext
-}
-
 // decryptKey 获取密码
 func decryptKey(encodeKey string) []byte {
 	// 移除非法字符
diff --git a/drivers/vtencent/util.go b/drivers/vtencent/util.go
index 91db54b7ad5..4ba72d1b2e3 100644
--- a/drivers/vtencent/util.go
+++ b/drivers/vtencent/util.go
@@ -8,9 +8,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"path"
 	"strconv"
-	"strings"
 
 	"github.com/alist-org/alist/v3/drivers/base"
 	"github.com/alist-org/alist/v3/internal/driver"
@@ -151,7 +149,7 @@ func (d *Vtencent) ApplyUploadUGC(signature string, stream model.FileStreamer) (
 	form := base.Json{
 		"signature": signature,
 		"videoName": stream.GetName(),
-		"videoType": strings.ReplaceAll(path.Ext(stream.GetName()), ".", ""),
+		"videoType": utils.Ext(stream.GetName()),
 		"videoSize": stream.GetSize(),
 	}
 	var resps RspApplyUploadUGC
diff --git a/internal/net/request.go b/internal/net/request.go
index c9ef363f2e0..d4f9321c585 100644
--- a/internal/net/request.go
+++ b/internal/net/request.go
@@ -619,10 +619,9 @@ type Buf struct {
 // NewBuf is a buffer that can have 1 read & 1 write at the same time.
 // when read is faster write, immediately feed data to read after written
 func NewBuf(ctx context.Context, maxSize int) *Buf {
-	d := make([]byte, 0, maxSize)
 	return &Buf{
 		ctx:    ctx,
-		buffer: bytes.NewBuffer(d),
+		buffer: bytes.NewBuffer(make([]byte, 0, maxSize)),
 		size:   maxSize,
 	}
 }
@@ -677,5 +676,5 @@ func (br *Buf) Write(p []byte) (n int, err error) {
 }
 
 func (br *Buf) Close() {
-	br.buffer.Reset()
+	br.buffer = nil
 }
diff --git a/pkg/utils/path.go b/pkg/utils/path.go
index c0793a3ec0f..135f8e4ebca 100644
--- a/pkg/utils/path.go
+++ b/pkg/utils/path.go
@@ -45,7 +45,7 @@ func IsSubPath(path string, subPath string) bool {
 
 func Ext(path string) string {
 	ext := stdpath.Ext(path)
-	if strings.HasPrefix(ext, ".") {
+	if len(ext) > 0 && ext[0] == '.' {
 		ext = ext[1:]
 	}
 	return strings.ToLower(ext)
diff --git a/server/common/proxy.go b/server/common/proxy.go
index 00fee4b2153..c14af6fad5e 100644
--- a/server/common/proxy.go
+++ b/server/common/proxy.go
@@ -1,97 +1,25 @@
 package common
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"io"
 	"net/http"
 	"net/url"
 	"os"
-	"strconv"
 	"strings"
 
-	"github.com/alist-org/alist/v3/internal/conf"
+	"maps"
+
 	"github.com/alist-org/alist/v3/internal/model"
 	"github.com/alist-org/alist/v3/internal/net"
-	"github.com/alist-org/alist/v3/internal/setting"
 	"github.com/alist-org/alist/v3/internal/stream"
 	"github.com/alist-org/alist/v3/pkg/http_range"
 	"github.com/alist-org/alist/v3/pkg/utils"
-	"github.com/microcosm-cc/bluemonday"
 	log "github.com/sirupsen/logrus"
-	"github.com/yuin/goldmark"
 )
 
-func processMarkdown(content []byte) ([]byte, error) {
-	var buf bytes.Buffer
-	if err := goldmark.New().Convert(content, &buf); err != nil {
-		return nil, fmt.Errorf("markdown conversion failed: %w", err)
-	}
-	return bluemonday.UGCPolicy().SanitizeBytes(buf.Bytes()), nil
-}
-
 func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.Obj) error {
-
-	//优先处理md文件
-	if utils.Ext(file.GetName()) == "md" && setting.GetBool(conf.FilterReadMeScripts) {
-		var markdownContent []byte
-		var err error
-
-		if link.MFile != nil {
-			defer link.MFile.Close()
-			attachHeader(w, file)
-			markdownContent, err = io.ReadAll(link.MFile)
-			if err != nil {
-				return fmt.Errorf("failed to read markdown content: %w", err)
-			}
-		} else if link.RangeReadCloser != nil {
-			attachHeader(w, file)
-			rrc, err := link.RangeReadCloser.RangeRead(r.Context(), http_range.Range{Start: 0, Length: -1})
-			if err != nil {
-				return err
-			}
-			defer rrc.Close()
-			markdownContent, err = io.ReadAll(rrc)
-			if err != nil {
-				return fmt.Errorf("failed to read markdown content: %w", err)
-			}
-		} else {
-			header := net.ProcessHeader(r.Header, link.Header)
-			res, err := net.RequestHttp(r.Context(), r.Method, header, link.URL)
-			if err != nil {
-				return err
-			}
-			defer res.Body.Close()
-			for h, v := range res.Header {
-				w.Header()[h] = v
-			}
-			w.WriteHeader(res.StatusCode)
-			if r.Method == http.MethodHead {
-				return nil
-			}
-			markdownContent, err = io.ReadAll(res.Body)
-			if err != nil {
-				return fmt.Errorf("failed to read markdown content: %w", err)
-			}
-
-		}
-
-		safeHTML, err := processMarkdown(markdownContent)
-		if err != nil {
-			return err
-		}
-
-		safeHTMLReader := bytes.NewReader(safeHTML)
-		w.Header().Set("Content-Length", strconv.FormatInt(int64(len(safeHTML)), 10))
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		_, err = utils.CopyWithBuffer(w, safeHTMLReader)
-		if err != nil {
-			return err
-		}
-		return nil
-	}
-
 	if link.MFile != nil {
 		defer link.MFile.Close()
 		attachHeader(w, file)
@@ -152,9 +80,7 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
 		}
 		defer res.Body.Close()
 
-		for h, v := range res.Header {
-			w.Header()[h] = v
-		}
+		maps.Copy(w.Header(), res.Header)
 		w.WriteHeader(res.StatusCode)
 		if r.Method == http.MethodHead {
 			return nil
diff --git a/server/handles/down.go b/server/handles/down.go
index b2f9a21bc63..1153881fdda 100644
--- a/server/handles/down.go
+++ b/server/handles/down.go
@@ -1,9 +1,12 @@
 package handles
 
 import (
+	"bytes"
 	"fmt"
 	"io"
+	"net/http"
 	stdpath "path"
+	"strconv"
 	"strings"
 
 	"github.com/alist-org/alist/v3/internal/conf"
@@ -15,7 +18,9 @@ import (
 	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/alist-org/alist/v3/server/common"
 	"github.com/gin-gonic/gin"
+	"github.com/microcosm-cc/bluemonday"
 	log "github.com/sirupsen/logrus"
+	"github.com/yuin/goldmark"
 )
 
 func Down(c *gin.Context) {
@@ -124,7 +129,34 @@ func localProxy(c *gin.Context, link *model.Link, file model.Obj, proxyRange boo
 	if proxyRange {
 		common.ProxyRange(link, file.GetSize())
 	}
-	err = common.Proxy(c.Writer, c.Request, link, file)
+
+	//优先处理md文件
+	if utils.Ext(file.GetName()) == "md" && setting.GetBool(conf.FilterReadMeScripts) {
+		w := c.Writer
+		buf := bytes.NewBuffer(make([]byte, 0, file.GetSize()))
+		err = common.Proxy(&proxyResponseWriter{ResponseWriter: w, Writer: buf}, c.Request, link, file)
+		if err == nil && buf.Len() > 0 {
+			if w.Status() < 200 || w.Status() > 300 {
+				w.Write(buf.Bytes())
+				return
+			}
+
+			var html bytes.Buffer
+			if err = goldmark.Convert(buf.Bytes(), &html); err != nil {
+				err = fmt.Errorf("markdown conversion failed: %w", err)
+			} else {
+				buf.Reset()
+				err = bluemonday.UGCPolicy().SanitizeReaderToWriter(&html, buf)
+				if err == nil {
+					w.Header().Set("Content-Length", strconv.FormatInt(int64(buf.Len()), 10))
+					w.Header().Set("Content-Type", "text/html; charset=utf-8")
+					_, err = utils.CopyWithBuffer(c.Writer, buf)
+				}
+			}
+		}
+	} else {
+		err = common.Proxy(c.Writer, c.Request, link, file)
+	}
 	if err != nil {
 		common.ErrorResp(c, err, 500, true)
 		return
@@ -150,3 +182,12 @@ func canProxy(storage driver.Driver, filename string) bool {
 	}
 	return false
 }
+
+type proxyResponseWriter struct {
+	http.ResponseWriter
+	io.Writer
+}
+
+func (pw *proxyResponseWriter) Write(p []byte) (int, error) {
+	return pw.Writer.Write(p)
+}


Test patch: