- SFTP模块:连接/断开/文件CRUD/系统信息采集/base64二进制写入 - 连接池:多服务器同时在线,瞬间切换profile - autoConnect:启动时自动连接所有非本地服务器 - 端口自动回退:listenWithFallback消除TOCTOU,解决端口冲突崩溃 - 文件服务器URL集中管理:file-server.ts消除8+处硬编码端口 - Sidebar设置面板:添加服务器/自动连接/自动刷新开关 - 修复:validateFilePath越界panic、正则预编译 - 修复:注释准确性(RemoveAll/端口8073/动态端口文档)
65 lines
1.8 KiB
Go
65 lines
1.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
// FileServerProxy 反向代理到内置文件服务器(用于媒体预览)
|
|
func (h *Handler) FileServerProxy(c echo.Context) error {
|
|
rawPath := c.Param("*")
|
|
if rawPath == "" {
|
|
return c.String(http.StatusBadRequest, "缺少文件路径")
|
|
}
|
|
|
|
clean := filepath.Clean(rawPath)
|
|
if strings.Contains(clean, "..") {
|
|
return c.String(http.StatusForbidden, "路径不允许包含 ..")
|
|
}
|
|
|
|
// 防止多重 /localfs/ 前缀(循环去除所有)
|
|
targetPath := filepath.ToSlash(clean)
|
|
for strings.HasPrefix(targetPath, "localfs/") || strings.HasPrefix(targetPath, "localfs\\") {
|
|
targetPath = strings.TrimPrefix(targetPath, "localfs/")
|
|
targetPath = strings.TrimPrefix(targetPath, "localfs\\")
|
|
}
|
|
c.Request().URL.Path = "/localfs/" + targetPath
|
|
h.fileProxy.ServeHTTP(c.Response(), c.Request())
|
|
return nil
|
|
}
|
|
|
|
// HTMLPreviewProxy 代理 HTML 预览请求(直连内部服务器,避免 ReverseProxy 路径拼接问题)
|
|
func (h *Handler) HTMLPreviewProxy(c echo.Context) error {
|
|
rawPath := c.QueryParam("path")
|
|
if rawPath == "" {
|
|
return c.String(http.StatusBadRequest, "缺少 path 参数")
|
|
}
|
|
clean := filepath.Clean(rawPath)
|
|
if strings.Contains(clean, "..") {
|
|
return c.String(http.StatusForbidden, "路径不允许包含 ..")
|
|
}
|
|
theme := c.QueryParam("theme")
|
|
|
|
targetURL := fmt.Sprintf("%s/localfs/html-preview?path=%s&theme=%s",
|
|
h.cfg.FileServerAddr(), url.QueryEscape(clean), url.QueryEscape(theme))
|
|
|
|
resp, err := http.Get(targetURL)
|
|
if err != nil {
|
|
return c.String(http.StatusBadGateway, "内部服务器不可用")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
for k, v := range resp.Header {
|
|
c.Response().Header()[k] = v
|
|
}
|
|
c.Response().WriteHeader(resp.StatusCode)
|
|
io.Copy(c.Response(), resp.Body)
|
|
return nil
|
|
}
|